Effective Traits

Published on

Estimated reading time: .

What if language primitives … weren’t primitive.

I didn’t pick the name don’t get mad at me for using it in jokes.


One of my favorite things about creating software is the capacity to recognize a problem and go about creating a solution to that problem.

One of my least favorite things about creating software is that while creating a solution, it is not uncommon to identify a problem, and then go create a solution to it.

In 2018, I decided I wanted to port some of Ball Aerospace’s OpenC3 (then called COSMOSrb) from Ruby to Rust. I got most of the way through writing a parser for their definition language when I realized I needed a way to represent bit-streams that could have arbitrary endianness and base element storage. I started writing bitvec, successfully used it in my Cosmonaught project, and then … realized I could make bitvec better.

I have not touched Cosmonaught since then.

While developing bitvec, I realized I needed a way to abstract over whether I was using Cell<> or atomic wrappers to access memory regions with shared write permissions. Around that time, a friend of mine also needed to do this, and we wound up writing radium.

Another major feature of bitvec is that it is generic over the underlying integer storage. The standard library doesn’t have unifying traits over the primitive integers, so I wrote my own in funty.

These were fairly small diversions that I was able to complete and go back to working on bitvec.

Except.

Another dimension of primitive types over which the standard library is not generic is pointer mutability. Like integers, the standard library uses macros to fill in templates for *const T and *mut T when they have common APIs. The documentation sidebar of the pointer primitive shows the result of this practice: most functions show up twice, once for their immut-ptr instance and once for their mut-ptr instance.

For a while, bitvec mirrored this. I used macros to create near-duplicate implementations of ${thing} and ${thing}Mut, fill in nearly the same fields and method on each of them, marking references as &$($mut)? T and letting the macro caller provide or withhold that marker, and generally being dissatisfied with what was happening. But, because bitvec is firmly committed to mirroring the exact API of the standard library as much as I possibly can, even when I don’t like it, most of these fraternal twins are going to have to stay (because they are slice iterators).

But not all of them.

About two (I think; it was definitely not during quarantine) years ago, I realized that I could copy my BitOrder work and create a trait and handful of implementors to represent different memory-access permissions, and then generalize a wrapper over pointers through this trait.

This experiment lived directly in bitvec::ptr for a while, then migrated to my workbench crate in wyz::comu when it grew enough to be generally-useful outside only bitvec’s needs, where it remained while I released bitvec 1.0.