Threads for snej


      Looks great! This covers just about everything I use spdlog for, and I could easily replace spdlog completely with these. Thanks for sharing it!


        Let me know if you have any trouble building. I was too lazy (for now) to make a new repo for these, but I tried to remove dependencies on the rest of the code.

        Oh, also, there is a Catch-based unit test file, tests/

    2. 2

      I remember when this was done on the Mac, back when they ran on the 68000. The 68000 had a physical 24-bit address bus, so plenty of software made use of the upper 8-bits, and then it took years for all that software to be cleaned up as soon as 68020s became popular (a 32-bit physical address bus). Looks like we still haven’t learned.


        I think it was the 030 that first supported 32-bit addresses.

        The problem wasn’t apps using the high bits themselves, but that the OS initially used them as an implementation detail of the memory manager, and some developers took shortcuts by testing or setting those bits directly in handles instead of using the official API calls like HLock / HUnlock. Then when the first 030 Macs rolled out, Apple changed the memory manager to store those tags elsewhere, and the apps that were groping the high bits would misbehave or crash.

        Apps using undocumented behavior and accessing memory directly instead of using accessor functions (a lot of the original Toolbox data structures were public structs/records) was one of the main reasons it took Apple so long to move the Mac to a modern OS architecture.


          The 68020 supported a 32-bit address bus.

      2. 1

        Vmem makes this a non-issue: if the OS supports a per-application opt in to larger address spaces, it just means applications self limit by using tagged pointers.

    3. 9

      Apple’s CoreFoundation library makes extensive use of low-bit pointer tagging. This allows it to store small integers (and maybe floats?), booleans and short strings directly in a pointer without allocating memory.

      The encoding gets pretty complex, especially for strings; IIRC there are multiple string encodings, one of which can crunch characters down to 5 bits so it can store a dozen(?) characters in a 64-bit pointer. This sounds expensive, but I assume there are SIMD tricks to speed it up, and it’s still going to be way faster than allocating and dereferencing a pointer.

      1. 3

        Any reference on the 5 bit encoding? This is in APIs that get called from Objective C or Swift?

        The low bit tagging seems like an obvious win and portable win these days, although I know Lua avoided it because it’s not strictly ANSI C. Rust also got rid of small string optimization early in its life, apparently due to code size

        Though honestly I would have expected fewer heap allocations and cache misses to be a win for most string workloads

        1. 5

          You got it — the reference I remember is from Mike Ash’s blog, which has been dormant for a few years, but the archives are a treasure trove of low-level info about Apple’s runtime.

          The CoreFoundation types are exposed in Obj-C as NSString, NSNumber, NSDate, NSValue. They also show up in Swift for bridging purposes, but the native Swift string and number classes are implemented differently (in Swift.)

        2. 1

          The various tagging schemes that objc (and by proxy swifts interop) uses are internal implementation details that can (and have) changed so it’s not API. Instead objc_msgSend and family handle it directly - similar to the myriad refcount stores and what not.

        3. 1

          I was actually looking at Mike Ash’s blog this week for info on tagged pointers:

          How about 5 bits? This isn’t totally ludicrous. There are probably a lot of strings which are just lowercase, for example. 5 bits gives 32 possible values. If you include the whole lowercase alphabet, there are 6 extra values, which you could allot to the more common uppercase letters, or some symbols, or digits, or some mix. If you find that some of these other possibilities are more common, you could even remove some of the less common lowercase letters, like q. 5 bits per character gives eleven characters if we save room for length, or twelve if we borrow a symbol and use a terminator.

          I was actually looking at the blog because I was wondering if ref counting in the unused 16 bits of a pointer might be feasible. It would give you up to 65k references, which is more than enough for many (most?) use cases. That would slim down the size of ref counted values and might make them as cache friendly as a GCed value. Might not be thread safe though.

          1. 3

            Wow, skimming the rest of the post, this is a lot more subtle than I would have expected, and also relatively recent – OS X 10.10 as of 2014.

            1. If the length is between 0 and 7, store the string as raw eight-bit characters.
            2. If the length is 8 or 9, store the string in a six-bit encoding, using the alphabet “eilotrm.apdnsIc ufkMShjTRxgC4013bDNvwyUL2O856P-B79AFKEWV_zGJ/HYX”.
            3. If the length is 10 or 11, store the string in a five-bit encoding, using the alphabet “eilotrm.apdnsIc ufkMShjTRxgC4013”

            The five-bit alphabet is extremely limited, and doesn’t include the letter b! That letter must not be common enough to warrant a place in the 32 hallowed characters of the five-bit alphabet

            Pretty crazy!

            I think if you control the entire OS and the same NSString is used everywhere, this makes more sense.

            For what I’m doing, we have to call into libc more, and pass it C strings, so we don’t control that part. The allocation to decode and make it look like a C string is problematic. Not just slow, but creates an ownership problem.

            1. 2

              I think if you control the entire OS and the same NSString is used everywhere, this makes more sense.

              It makes me wonder if that is a headwind for adoption to Swift on other platforms. Is the language so tuned to performance on a single platform that it makes the code difficult to port?

              It seems like they are also doing some pretty intricate things with C++ interop. As a outside observer (and a relatively ignorant one at that), it seems like it would be very difficult to generalize some of this work.


                This stuff is part of CoreFoundation, not Swift. Swift on Apple platforms has some pretty sophisticated interop / FFI with it, for compatibility with Objective-C code, but that isn’t present in Swift on other platforms.

              2. 3

                It shouldn’t be hard to port. In GNUstep, we adopted a compressed strings in pointers encoding some years before Apple (not the 5-bit one. Doing that well requires some analysis of data that I didn’t have access to from run-time profiling of a large set of real-world applications). The interface for iterating over strings allows the caller to provide a buffer. These strings are, by definition, of small bounded length and so converting to a C string for interoperability is trivial with the caller providing the buffer on its stack.

                It does have some very nice performance properties. A lot of dictionaries use small strings as keys. If you check the length on the way in and try to convert mutable strings used for lookup to small strings then you know that the result either is or isn’t a small string. This lets you skip a bunch of comparisons after you’ve found the hash bucket.


                Not really. In fact, Swift on Linux doesn’t have certain complexities that are present on Apple platforms due to the necessity of ObjC interop.

              4. 2

                I haven’t followed Swift closely, but I do have the feeling that the focus is entirely on Apple’s platform, and there are some fairly hard tradeoffs with respect to portability / open source.

                Just like I think of Google’s entire stack as a vertically integrated embedded system (hardware up to cloud and apps), Apple seems to be architected in a similar way. They get some pretty big benefits from the vertical integration and control

                Andreas Kling mentioned that Apple is a huge inspiration for SerenityOS – basically doing everything yourself and not taking dependencies, which is kind of the opposite of most open source, which is about reuse and portability

                It seems like if Swift were going to be popular on Linux or Windows, that would have already happened by now. Looks like it’s about 9 years since the first release now


            You can’t put the ref-count in a pointer, because a pointer is a reference. If you increment a count in a pointer, that doesn’t affect any other pointers to the object, so no one else knows you added a reference.

            CoreFoundation does (IIRC) store refcounts outside objects. My memory is hazy, but it might reserve a few bits for an internal refcount, and when that pins at its max value, the real refcount moves to a global hash table. The answer probably lies in Mike Ash’s blog. (Swift-native class objects don’t do this, though.)

            [Update: just saw your other post below. What CF does uses spare bits in a pointer field inside the object; I thought you meant putting the refcount in pointers to the object.]


              No, you were right in the first place. I was totally thinking about things wrong. I got wrapped around the tree a bit. Thank you for the correction.


              It was this one - I added a link to it this morning after this thread!

          3. 2

            The refcount for a normal objc object is kept in a few points of the isa pointer, and then moved to a side table once the refcount exceeds the available bits. The result is that there’s no major memory overhead to the refcount in normal objects.

            1. 2

              I did see that in this article from Mike Ash’s site:


              It made me happy to see that it wasn’t a totally stupid idea on my part! :]


        Yup. Plus the WebKit “Gigacage” and the v8 (Javascript runtime) uses this mechanism for sandboxing. I hear another browser engine is considering something similar.

      3. 2

        On Intel, the 5 bit encoding can be optimized by using pdep to expand each 5 bits into a full byte, and pshufb to do a SIMD table lookup. I don’t think Arm has something like pdep though.


          Pdep is a bit overpowered. You can also do it with a multishift, though arm doesn’t have those either…


            I don’t know much about ARM, but I guess you could:

            • Broadcast the lower and upper 32 bits of the NSString each into a simd register v1, v2.
            • Shift and mask each lane by a separate amount so that the relevant 5 bits are aligned with 16 bit lanes (and the relevant lanes don’t overlap between v1, v2).
            • Or v1, v2
            • Use a tbl instruction to recostruct the original order.
      4. 1

        Storing tags in a few low bits isn’t really what this is about I think, as changes to total address space (adding high bits) don’t really impact it.

        1. 5

          The article did talk about it, though.

        2. 2

          It’s not what led me to start writing the article but it’s definitely relevant. It’s also probably used way more in practice than putting data in the upper bits. Thanks @snej for the additional note - I actually came across some of the Mike Ash blog posts last night too. I’ll add a reference to them in.

          1. 7

            For CHERI C, we found a lot of cases of storing things in the low bits (not least in LLVM’s compressed integer pointer pair template). This is a big part of why we support (on big CHERI) out of bounds representation beyond the end of a pointer’s range: so you can store data in the low bits of a pointer to the end of an array (you can’t dereference it without bringing it back).

            Most (not quite all) of the places where things stored dat in the top bits, they were creating tagged unions of a pointer and some other data and rarely storing anything in the top bits other than a ‘this is not a pointer’ pattern, which can be replaced by the tag bit on CHERI (giving a full 128 bits of space for other data, something I started to explore but didn’t pursue very far and which I expect to be very useful for dynamic languages). A small number of things stored a a pointee type in the high bits.

            Morello separates the value field of the capability into address and flags. With TBI enabled, you can store data in the top 8 bits. I believe this was mostly done to allow MTE composition later. It has some fun implications on arithmetic.

    4. 6

      Man, this takes me back — my first full-time job out of college ended up being to build a complete font-design app. (I was initially just supposed to work on one piece, but the other guy ended up being useless so they fired him and gave all the work to me…)

      I’ve never tried making a font myself! But I’ve looked at enough of them to know that making a good font is very hard, even if you’re scanning samples of an existing design. Of course doing one of your printing is easier.

      One tip: there are some stock words and phrases that typographers often look at when designing; the one that sticks with me is “hamburgefons”. These are chosen to contain lots of different components like ascenders and descenders, and pairs of adjacent letters that are often problematic and need kerning. I’m sure you can find a bunch more by googling.

      Choosing something like OTF is unnecessary because I don’t plan on storing many glyphs (I don’t specifically want a bold or italic font, and if I ever do I can make it a separate font), nor do I need ligatures.

      You can do ligatures and alternates in TTF, if I’m not mistaken. These are useful for “organic” looking handwriting fonts because they let you create natural looking joined letter pairs, and with alternates you can have different forms of a letter that get chosen semi-randomly so that e.g. all the “e”s don’t look alike. Of course it’s a lot more work!

    5. 4

      tl;dr: These are slightly-simplified versions of spdlog and std::format with a much, much smaller code size, i.e. 16KB instead of 150-200KB.

      I’ve been a happy user of spdlog for a while, but when I started building one of my projects for ESP32 microcontrollers I realized how much code bloat it adds; most of that’s due to std::format, which appears to work by inlining all the formatting calls at the call-site.

      I had a lot of fun stretching my template and constexpr skills by reimplementing these, especially the formatting! The secret sauce to keep code size small is to pass arguments by plain-old varargs instead of template “parameter packs”. That way I have a single function that does the formatting, and the call site is simple. To preserve type-safety, a synthetic arg is created that’s a list of the types of the real args; the implementation iterates that list so it knows what types to use with va_arg(). Source code is here.

    6. 3

      This is a 6502 instruction, right?

      Nope, I just looked up “sm83” and it’s a Z80 variant. I knew I (dimly) recalled the DAA instruction from my teens.

      For some reason, I can never keep straight which Nintendo system used which CPU…

      1. 1

        I haven’t looked at the 6502 much at all, but it seems it has a decimal “mode” which does the same thing as this instruction

        1. 2

          snej mentioned Nintendo, interestingly the 6502 core in the NES’s main processor had the BCD circuitry disconnected/removed. It’s been said this was for patent reasons: while the chip design was broadly unprotected by copyright, the 6502’s BCD was under patent.

    7. 13

      It’s weird to compare to OpenSSL and not mBedTLS or WolfSSL, which (between them) own most of the market that this seems to aim for.

      Quickly glancing at the code, it looks as if the bignum operation all have data-dependent timing and so this library almost certainly contains exploitable side channels (I would expect an attacker on a local network to be able to leak keys fairly easily).

      1. 3

        Yeah, I like this library in principle, but seeing new implementations of crypto primitives gives me the same feelings as seeing an abandoned backpack in an airport before the bomb squad robot has poked through it.

    8. 2

      The approach I’ve always heard of is to use a binary heap, since it’s O(log n) and the constant factor is pretty low since it just consists of pointer swaps in a very compact array.

      I am wondering when it becomes useful to move to the other later-numbered schemes. When the number of tasks or the insertion rate becomes huge?

      Also, the paper assumes you will be checking timers every tick, which seems IMO wasteful in many cases. I can see doing it that way in an RTOS implementation where you’re servicing a timer interrupt, or if your process is so busy that there’s nearly always work to do on every tick; but otherwise it’s much better to compute when the next timer will go off and then sleep until then, which the heap is perfect for.

      1. 2

        Normally these are used one layer up from the kernel. If you have a process with a load of callbacks that need timers (e.g. one per connection in a server with tens of thousands of clients), you create a wheel arrangement and then find the first timer value and pass that to kqueue or equivalent. Then you handle everything on that trigger, then advance the week, find the next sleep duration, pass that to kqueue, and repeat.

        I haven’t looked, but I’d be quite surprised if NSRunloop doesn’t use a hierarchical timing wheel for NSTimers. I think the Apple one now wraps CFRunloop, so I probably could look at the source.

    9. 6
      • iPadOS. Sometimes I realize with awe that I have an actual goddam Dynabook in my hand, 50+ years after Alan Kay first put the idea out into the world and 40-ish years after the concept first blew my child mind.
      • Clang. I am thankful that I have a fast compiler with enough guard-rails to catch most of my stupid mistakes. (I say “fast” because my first experience of C++ was cfront running on a Mac IIfx.)
      • Scrivener. A damn good word processor for writers.
      • All the crazy DSP code that runs my guitar amp (Fender GTX-50) and half my pedals. I love being able to turn a knob and choose from dozens of amps and effects. I love being able to mangle my sound in creative ways — special shout out to the Hologram Microcosm.
      • VS Code, for making wrangling Go and TypeScript so much less onerous.
      • Fork, for making Git a joy to use.
      • libuv, for saving me from the sanity-shredding eldritch horrors of cross-platform networking code.
      • SQLite, for just being a damn good reliable data store.
    10. 1

      I was hoping this was the original MIT Zork / Dungeon which Infocom carved up into three pieces because it was too big for the Z-machine. (That one was written for a weird Lisp dialect called MDL that ran on a PDP-10 IIRC, but also IIRC it was ported to the PDP-11.)

      I’m not really seeing the point of running the regular old Zork, just more inconveniently in an interpreter for an old machine you have to run in its own emulator, but I know some people just dig retro stuff.

    11. 2

      The article itself seems kinda content-free to me, just saying “there are lots of ways to do stuff and consistency is good I guess?”

      The linked set of actual guidelines has more meat, but I disagree with about half of them.

      Do not use C-style casts.

      I agree in principle and I know why they’re dangerous, but I haven’t been able to wean myself off them because they’re so nice and compact compared to spelling out static_cast etc.

      Initialize all variables at declaration.

      No, not always. This used to be good advice before static analyzers got good, but today it can be dangerous, because it can prevent static analysis from doing its own checks for uninitialized variables. Let’s say you have a variable whose value depends on some condition. It first gets declared, and then assigned a value in both sides of an “if” statement. And let’s say you forget to actually assign the value to it in one side. If you declare the variable without a value, the compiler will warn later on that you are using a variable that hasn’t been initialized in all control paths. Nice! If you initialized it with a placeholder value like 0 or null, you won’t get that warning. Now you have a bug that only shows up at runtime.

      Use const and nodiscard whenever you can (but no const for member variables).

      const for members can be annoying because it prevents assignment of the containing object, but that only matters for value types, so I don’t see a reason to ban it, because it’s very useful to have immutable state.

      No manual memory management using new, delete, malloc, free, etc.

      A good principle, but in reality you sometimes need to, either to interact with C APIs, or for performance reasons because C++ memory APIs still have no equivalent of realloc.

      Do not use volatile, const_cast or reinterpret_cast, typedef, register or extern.

      • mutable mostly gets rid of the need for const_cast but not always. I frequently use it when implementing a method that has both const and non-const versions, so I can implement one as a call to the other instead of duplicating code.
      • reinterpret_cast is admittedly sort of evil, but absolutely necessary sometimes in the kind of low level code C++ excels at, like implementing a memory allocator or garbage collector.
      • There’s no workaround for extern if you need to declare a constant and can’t put the definition inline in the header.
      1. 4

        Initialize all variables at declaration.

        No, not always. This used to be good advice before static analyzers got good, but today it can be dangerous

        Yeah, I often point this out in code reviews. Uninitialized variables, in cases where no good default exists, are back to being a good thing. (EDIT: assuming the project has some static analysis set up that would catch it…)

        const for members can be annoying because it prevents assignment of the containing object, but that only matters for value types, so I don’t see a reason to ban it, because it’s very useful to have immutable state.

        The other big reason is that it disables the type’s move-constructor, so it can be a silent performance pessimization.

      2. 3

        I agree in principle and I know why they’re dangerous, but I haven’t been able to wean myself off them because they’re so nice and compact compared to spelling out static_cast etc.

        I think this is one of the reasons that they’re a bad idea. A C-style cast may include more than one dangerous operation. If you spell it out in C++ casts then it looks obviously dangerous and draws the attention of code reviewers.

        This used to be good advice before static analyzers got good, but today it can be dangerous

        Not just static analysers. Both clang and gcc have some basic path-based analysis that will warn if you’re using an uninitialised variable at compile time and you can turn it into an error with a single compiler flag (note: in addition to -Werror, both compilers support -Werror-{warning name} to turn specific warnings into errors). Turn this on and you’ll catch far more bugs than you will initialising variables at initialisation point.

        That said, variables that are initialised with the same value unconditionally should be initialised at declaration. There’s a nice trick for things that require multiple statements to initialise (e.g. do something while holding a lock): initialise them with the result of calling a local lambda. The lambda has a single caller so will always be inlined and you get the same code you’d get without the lambda.

        const for members can be annoying because it prevents assignment of the containing object, but that only matters for value types, so I don’t see a reason to ban it, because it’s very useful to have immutable state.

        Agreed. And sometime breaking assignment is a desirable property.

        A good principle, but in reality you sometimes need to, either to interact with C APIs, or for performance reasons because C++ memory APIs still have no equivalent of realloc.

        That’s probably a good thing. Realloc is only efficient in range-based allocators and they are slow for everything else. A modern sizeclass-based allocator will be expand realloc into malloc, memcpy, free, for all situations where the new size is a different sizeclass. Using an allocator where realloc is fast is almost certainly going to result in a slowdown.

        mutable mostly gets rid of the need for const_cast but not always.

        I mostly use const_cast for removing volatile on APIs that need volatile for C99 interoperability.

        reinterpret_cast is admittedly sort of evil, but absolutely necessary sometimes in the kind of low level code C++ excels at, like implementing a memory allocator or garbage collector.

        I think it’s good advice to not use it. Any set of guidelines has exceptions and this means that, in code review, you need to be able to justify why you need it. For most code, you don’t need it, for the places where you do, you really need to understand low-level details and, if you do, you know that it’s sometimes okay to break the guidelines.

        It also encourages you to put the reinterpret cast in a templated wrapper that checks some other domain-specific property at compile time to make sure that it’s actually safe.

        1. 1

          Realloc is only efficient in range-based allocators and they are slow for everything else. A modern sizeclass-based allocator will be expand realloc into malloc, memcpy, free, for all situations where the new size is a different sizeclass.

          But it’s still more efficient (a no-op) if the new size is the same sizeclass, right? I’m thinking about situations where I allocate a conservatively-sized buffer up front, then write into it, then realloc it to the actual used size. The realloc says “copy this if it’ll free up memory, but don’t bother if it won’t.” The goal being to optimize memory usage, not speed.

          (Are sizeclass allocators used for the default malloc nowadays on most platforms? What about embedded systems — I’ve been doing some ESP32 work lately and even running into the ancient curse of out-of-memory errors…)

          1. 2

            The most common malloc() that doesn’t use size classes is dlmalloc and its derivatives ptmalloc and glibc malloc.

            Er, but having said that I don’t know whether macOS and Windows do or don’t.

          2. 1

            But it’s still more efficient (a no-op) if the new size is the same sizeclass, right?

            Typically, if the size is less than the result of calling malloc usable size (or local equivalents), but larger than the sizeclass one down, they will do nothing.

            The realloc says “copy this if it’ll free up memory, but don’t bother if it won’t.” The goal being to optimize memory usage, not speed.

            Yes, that situation still makes sense.

            Are sizeclass allocators used for the default malloc nowadays on most platforms?

            Glibc still ships with a range-based allocator, which is why you can often get a 30% speed up by linking a different malloc. I think everywhere else ships a sizeclass allocator of some kind…

            What about embedded systems

            … except here. Sizeclass allocators have more memory (and, especially, more address-space) overhead than range-based ones. We didn’t even try to fit a sizeclass allocator in CHERIoT RTOS because our lowest-bound estimate of overhead was unacceptably high. Mind you, a lot of mallocs on embedded systems are just plain bad. The ThreadX one has some O(n) behaviours on hot paths that could be O(log(n)) with no size increase.

    12. 12

      A language that explicit targets privileged and networked services shouldn’t be relying on the hope that “some safety” will be enough.

      UAFs are not a small problem. They make up a huge portion of real world memory safety vulns. Removing spatial vulns with bounds checking is huge, don’t get me wrong, but I would be uncomfortable with a privileged Hare service.

      I’m a bit pessimistic though. I think hardened allocators are quite interesting and, if one were to simply remove all spatial vulns, I admit that I am curious to see what practical exploitation ends up looking like.

      1. 7

        Agreed; any language without some form of automatic memory management (whether GC or ARC) is a hard pass for me.

      2. 1

        but I would be uncomfortable with a privileged Hare service.

        I think in the context of their helios microkernel I would be more comfortable with it.

        1. 3

          Possibly, yeah. The reality is that we’re in new territory in terms of “what if we actually put a bit of effort into security”. Like ~30 years of mainstream software considering this to be a thing worth pursuing. Not enough time to answer questions like “what if we solved spatial but not temporal safety?” or “what if we had a microkernel with a capabilities system, and ‘mostly’ safe userland?”.

          Again, academically I’m quite curious. In terms of caring about users, I wish we’d wait to answer those questions until we actually had safe systems.

        2. 2

          This isn’t quite “boil the ocean” territory, but the water is getting warmer…[1]

          [1] I realize that this idiom is feeling less and less implausible as the planet heats up.

    13. 12

      I like this one; I’ll be giving it a try.

      Minor note: Typographers often hide funny phrases in type specimens; the old ATF catalogs are full of them. They’re often driven by the constraint of exactly filling a line without adding extra space. In the specimen here I noticed “OldButSanePotus”, which is such an apt description of Joe Biden…

      1. 2

        I kind of like it too, and have been trying it. I’ll give it a little more time to see where I land, but I suspect I’ll probably switch back to JetBrains Mono in my editor and keep this around to use for code snippets in slides. My early results are that I find the italic style a little distracting as I’m writing code, but I really like it for reading code.

    14. 3

      I hope some concatenative experts will weigh in! I’m more of a tourist.

      This is the first prefix-oriented concatenative language I’ve seen. I can see how it might be more efficient in some ways, but it still requires stacks to track pending functions and assemble their arguments, right?

      To me the code seems harder to read because basically being evaluated from right to left, not left to right as in Forth; however that does mean one reads the functions before their operands, which is more natural. I guess this is similar to reading functional code only without [most of] the parentheses.

    15. 13

      Oh no )-: another step towards moving basic functions to web and web-browsers. Why?

      I used to have a shortcut (when I ran dwm) to open a text editor with a scratchpad file. Now I have a shortcut to open a terminal and a single letter shell alias to open a scratchpad file for editing.

      I bet most people can’t multiply two numbers without sending a bunch of HTTP request to Google search engine anymore )-:

      1. 6

        I wrote about why I reimplemented typehere:

        The utility of the tool is only part of the reason, the other being nostalgia.

        A local scratch-pad is great, I use i3 and have shortcuts for opening an editor too. More often than not, I usually use my local scratch-pad, but it’s nice to always have something I can open on a machine that’s not set-up by me.

        If you aren’t convinced, that’s fair. Just letting you know that it was more a labour of love.

        1. 2

          I sense my remark might have landed unwell. If you took it personally (at your work or at yourself), I’m sorry. It was just sort of “an old man yelling at a cloud” kind of remark. Thank you for the explanation and more context provided.

          1. 1

            Tone is lost in text. Your remark landed well, rest assured, no need to apologize. I merely wanted to post my rationale for posterity. I appreciate the follow-up nonetheless.

      2. 1

        I don’t see how this moves anything to the browser

        1. 4

          It moves text editing/ note taking, as in everything from vi to NotePad to Obsidian, into a web browser window.

          I don’t understand why anyone would want to do that, but I accept that nostalgia is a powerful motivator.

    16. 2

      I never understood why people like the thing. I just use a Kensington trackball and never look back.

      1. 2

        I really liked the Magic Mouse when I tried it. The mouse bit works like a mouse, but all of the scroll gestures that I use on a trackpad work well as well. The charging port on the underside is just such a fantastically poor design decision that I can’t understand how it made it into production, let alone remained there for multiple revisions.

        Now that Apple is supporting wireless charging on iPhones, I wonder how long until we see mice with a charging mousemat. You can use it anywhere with the battery full, but have to use the mouse mat when it’s empty. Leave it on the mousemat overnight to charge.

        A lot of other wireless mice over seen still use AA batteries, which feels wasteful. The MS keyboards have some very nice pairing logic (not sure if this is based on a standard) where you connect the, via USB and they then do the Bluetooth key exchange over USB, so they’re then paired when you unplug them. This gives a strong incentive to design devices that can be used and charged over USB. Sadly, their mice don’t support this mode (yet?).

        1. 2

          Now that Apple is supporting wireless charging on iPhones, I wonder how long until we see mice with a charging mousemat.

          Dunno about mice, but there are fancy wireless keyboards that can charge wirelessly from a desk mat.

        2. 2

          I guess you didn’t try it very long.

          The only people I’ve heard complaining about the charging port are people who don’t use it. People who do use it, are aware how much of a non-issue it is.

          1. 1

            No, it wasn’t mine. But I’ve run out of batteries on other wireless mice enough to not want one that I have to stop using to charge. I guess if you’re in the habit of charging it regularly it becomes a non-issue, but I don’t really want to have to remember that. With removable batteries, it’s wasteful to throw the old ones away. With other rechargeable mice I’ve used, they fall back to being wired when the batteries are flat for a bit.

            Why is it a non-issue?

            1. 5

              If it runs out during the day (and it will have given you numerous warnings on-screen before that happens) the time required to charge it enough for a full 8 hour workday is literally about 2 minutes. The actual literally, not that ironic use where they mean figuratively. 120 seconds.

              I can’t even make a coffee in that much time, and the coffee machine is in the same room.

              1. 1

                Thanks, that’s good to know. There’s a local shop that sells them second hand very cheaply, I’ll try to pick one up next time I’m there.

      2. 2

        I started weaning myself off Apple input devices in the last few years, in response to their crappy keyboards. This included digging out my old Kensington Expert Mouse (which is a big trackball despite its name) to replace the standalone trackpad, and for fun I started making my own keyboards. The advent of the M1 macs made me less keen to move away, but homemade keyboards remain fun.

      3. 1

        Um, different input strokes for different folks? Me, I never understood the appeal of trackballs, except of course for playing Centipede or Marble Madness. Also I hate avocados and think Daft Punk are repetitive crap. Discuss in the comments.

        Unlike those subjective opinions, the Magic Mouse is provably the best mouse because its surface is a multitouch trackpad. In addition to allowing horizontal scrolling, it also supports gestures like multi-finger swipes and taps.

        And yeah, every few weeks I have to stop using it for an hour to let it charge. Is that ideal? No. But the sucky conventional mouse I use during that one hour reminds me why I use the Magic Mouse.

        1. 2

          My main complaint about the apple dark sorcery mouse was that it made my hand feel like it had arthritis within the first 10 minutes of using it. So I was never able to even appreciate any of the input features because it hurt my hand so much.

        2. 1

          FYI if you charge it for like 2 minutes it’ll get a full days charge, if you then remember to charge it overnight it’ll get a month worth of charge.

    17. 1

      It’s a short survey; they were right that it only takes a few minutes.

      I answered that I stopped using Nim, both because of continuing frustration at half-completed buggy features (compile-time null checking that breaks if you put a return in a function was the last straw) and because the BDFL is too unpleasant to deal with (i.e. blamed me for using early returns because he thinks it’s bad programming style.)

    18. 1

      I know not what this “QString” thing is. Part of some nonstandard library. Odd thing to use in an example.

      You can write \N{NAME} in C++23 source and it compiles down to a single Unicode character, which becomes however many bytes it needs to be.

      I’m a bit confused by that — cppreference is vague about what encoding is used. It kind of implies that in a regular string literal it’s UTF-8, but it doesn’t say so, it just gives UTF-8 as an example:

      A universal character name in a narrow string literal or a 16-bit string literal may map to more than one code unit, e.g. \U0001f34c is 4 char code units in UTF-8 (\xF0\x9F\x8D\x8C) and 2 char16_t code units in UTF-16 (\xD83C\xDF4C).

      So either it’s always UTF-8, or it’s whatever encoding the source file uses.

      1. 2

        The painful thing is that C and C++ (which inherited this from C) do not mandate a character encoding for source. This was to allow things like ASCII or EBCDIC as inputs. I think C can work with 6-bit encodings. If you provide anything else, the compiler has to do some translation. This leads to fun if you write a character in UTF-8 but the person running the compiler has an 8-bit code page locale set. I ran into some very interesting bugs from non-ASCII characters in string literals when the code was built on a machine with a Thai or Japanese locale. GCC would parse the character in the user’s local and then emit it as the corresponding point in some other encoding (UTF-8?). Or, in the opposite direction, they’d type a character that their editor would save according to their locale’s charset and clang would pass it through. In both cases, the code expecting UTF-8 would then do the wrong thing (sometimes very excitingly if the byte sequence wasn’t even valid UTF-8).

        C++20 finally provided string formats that are guaranteed to output UTF-8 and, as long as your source is also UTF-8, everything is fine. I wish C++26 would just sat ‘source code must be UTF-8’. I doubt there’s much code that’s deliberately something else, and it’s a one-time mechanical translation to fix.

        1. 1

          I think C can work with 6-bit encodings.

          C’s basic character set has 74 characters and each basic character must be encoded as one byte. So it does not quite work for 6 bit character sets - I think historically 6-bit charsets were upper-case only which would make it tricky to write C.

          Which reminds me that Unix tty drivers had support for upper-case-only terminals - there were oddities like, if you gave login an upper-case-only username, it would switch the terminal into case smashing mode. I have no idea how well this worked in practice, or if it was cripplingly awkward.

          Back to C, I think the most restricted character sets it aimed to support were ISO-646, i.e. the international versions of ASCII that had things like nordic letters instead of []\

      2. 1

        QString is Qt’s string type and this guy is a Qt / KDE (desktop built on Qt) developer, so I guess thats some context that is missing. Like CString.

    19. 1

      That is an extremely specific, and weird, use case. I guess for this one person, source_location is broken.

      What I thought this article was going to be about is that source_location is totally broken in whichever version of clang is in Xcode 15. It doesn’t give you the location of the call site (the caller of the function with the default-initialized source_location parameter), it gives the location of the declaration of said function. I have compiled the very example given in cppreference and it produces the wrong output. I filed a bug report with Apple, who will presumably pass it on to clang unless it’s something they broke in their forked version.

    20. 13

      I was nodding along, until the mIRC screenshot gashed my eyes. Ow. What a monstrosity (like most Win95 UIs.) If they wanted an example of the laudable principles listed below it, surely there were better choices.

      1. 16

        I think the point was that mIRC is an incredibly low bar and yet a lot of modern apps still manage to be worse.

      2. 15

        I’ll take the mIRC UI over the flat ones any day of the week.

        I personally can’t stand the “UIs need to be ‘beautiful’” meme. Software is a tool, and a “beautiful tool” is one that’s well crafted and gets the job done efficiently.

        Too many UX designers seem detached from the users of the UIs they design, and treat a “beautiful UI” the same as a “beautiful artwork”. Software isn’t hung on a wall to admire, it’s used and worked with.

        1. 6

          Well-crafted and beautiful are more similar than you think. All the sharp edges and high contrast in the Win95 UI add visual noise, and the windows-within-windows thing is confusing because the layering is hard to discern and inner windows get clipped by outer ones.

          I agree that many designers focus too much on aesthetics alone, but that doesn’t mean aesthetics aren’t important and visual style doesn’t affect usability.

          1. 2

            Did you know that cities are often replacing “noise restrictions” with “sound restrictions”? What your neighbor calls “noise” you simply call “band practice”… does it violate the law?

            Who is to say those borders “visual noise” vs “clear functional distinctiveness”? I think this is the author’s point about asking where the usability studies are.