Now you can construct a `Field` only if the given value fits! Otherwise, you have `None`, which signals that an error has occurred, rather than lopping off the high bits of the value and silently writing an unexpected value.
Note, though, this will raise an error at runtime. However, we knew the value we wanted to write beforehand, remember? Given that, we can teach the compiler to reject entirely a program which has an invalid field value—we don’t have to wait until we run it!
This time, you'll add a _trait bound_ (the `where` clause) to a new realization of new, called `new_checked`, that asks the incoming value to be less than or equal to the maximum possible value a field with the given `Width` can hold:
value: (V::U8 << Offset::U8) & Mask::U8,
_mask: PhantomData,
_offset: PhantomData,
_width: PhantomData,
}
}
}
```
Only numbers for which this property holds has an implementation of this trait, so if you use a number that does not fit, it will fail to compile. Take a look!
`new_checked` will fail to produce a program that has an errant too-high value for a field. Your typo won't blow up at runtime because you could never have gotten an artifact to run.
You're nearing Peak Rust in terms of how safe you can make memory-mapped hardware interactions. However, what you wrote back in the first example in C was far more succinct than the type parameter salad you ended up with. Is doing such a thing even tractable when you're talking about potentially hundreds or even thousands of registers?
### Just right with Rust: both safe and accessible
Earlier, I called out calculating masks by hand as being problematic, but I just did that same problematic thing—albeit at the type level. While using such an approach is nice, getting to the point when you can write any code requires quite a bit of boilerplate and manual transcription (I'm talking about the type synonyms here).
Our team wanted something like the [TockOS mmio registers][7], but one that would generate typesafe implementations with the least amount of manual transcription possible. The result we came up with is a macro that generates the necessary boilerplate to get a Tock-like API plus type-based bounds checking. To use it, write down some information about a register, its fields, their width and offsets, and optional [enum][8]-like values (should you want to give "meaning" to the possible values a field can have):
```
register! {
// The register's name
Status,
// The type which represents the whole register.
u8,
// The register's mode, ReadOnly, ReadWrite, or WriteOnly.
From this, you can generate register and field types like the previous example where the indices—the `Width`, `Mask`, and `Offset`—are derived from the values input in the `WIDTH` and `OFFSET` sections of a field's definition. Also, notice that all of these numbers are `typenums`; they're going to go directly into your `Field` definitions!
The generated code provides namespaces for registers and their associated fields through the name given for the register and the fields. That's a mouthful; here's what it looks like:
```
mod Status {
struct Register(u8);
mod On {
struct Field; // There is of course more to this definition
}
mod Dead {
struct Field;
}
mod Color {
struct Field;
pub const Red: Field = Field::<U1>new();
// &c.
}
}
```
The generated API contains the nominally expected read and write primitives to get at the raw register value, but it also has ways to get a single field's value, do collective actions, and find out if any (or all) of a collection of bits is set. You can read the documentation on the [complete generated API][9].
### Kicking the tires
What does it look like to use these definitions for a real device? Will the code be littered with type parameters, obscuring any real logic from view?
No! By using type synonyms and type inference, you effectively never have to think about the type-level part of the program at all. You get to interact with the hardware in a straightforward way and get those bounds-related assurances automatically.
Here's an example of a [UART][10] register block. I'll skip the declaration of the registers themselves, as that would be too much to include here. Instead, it starts with a register "block" then helps the compiler know how to look up the registers from a pointer to the head of the block. We do that by implementing `Deref` and `DerefMut`:
When we're working with runtime values we use `Option` like we saw earlier. Here I'm using `unwrap`, but in a real program with unknown inputs, you'd probably want to check that you got a `Some` back from that new call:[1][11],[2][12]
Depending on your personal pain threshold, you may have noticed that the errors are nearly unintelligible. Take a look at a not-so-subtle reminder of what I'm talking about:
```
error[E0271]: type mismatch resolving `<typenum::UInt<typenum::UInt<typenum::UInt<typenum::UInt<typenum::UInt<typenum::UTerm, typenum::B1>, typenum::B0>, typenum::B1>, typenum::B0>, typenum::B0> as typenum::IsLessOrEqual<typenum::UInt<typenum::UInt<typenum::UInt<typenum::UInt<typenum::UTerm, typenum::B1>, typenum::B0>, typenum::B1>, typenum::B0>>>::Output == typenum::B1`
--> src/main.rs:12:5
|
12 | less_than_ten::<U20>();
| ^^^^^^^^^^^^^^^^^^^^ expected struct `typenum::B0`, found struct `typenum::B1`
The `expected typenum::B0 found typenum::B1` part kind of makes sense, but what on earth is the `typenum::UInt<typenum::UInt, typenum::UInt…` nonsense? Well, `typenum` represents numbers as binary [cons][13] cells! Errors like this make it hard, especially when you have several of these type-level numbers confined to tight quarters, to know which number it's talking about. Unless, of course, it's second nature for you to translate baroque binary representations to decimal ones.
After the `U100`th time attempting to decipher any meaning from this mess, a teammate got Mad As Hell And Wasn't Going To Take It Anymore and made a little utility, `tnfilt`, to parse the meaning out from the misery that is namespaced binary cons cells. `tnfilt` takes the cons cell-style notation and replaces it with sensible decimal numbers. We imagine that others will face similar difficulties, so we shared[`tnfilt`][14]. You can use it like this:
Memory-mapped registers are used ubiquitously when interacting with hardware from software, and there are myriad ways to portray those interactions, each of which has a different place on the spectra of ease-of-use and safety. We found thatthe use of type-level programming to get compile-time checking on memory-mapped register interactions gave us the necessary information to make safer software. That code is available in the `[bounded-registers][15]crate` (Rust package).
Our team started out right at the edge of the more-safe side of that safety spectrum and then tried to figure out how to move the ease-of-use slider closer to the easy end. From those ambitions, `bounded-registers` was born, and we use it anytime we encounter memory-mapped devices in our adventures at Auxon.
1. Technically, a read from a register field, by definition, will only give a value within the prescribed bounds, but none of us lives in a pure world, and you never know what's going to happen when external systems come into play. You're at the behest of the Hardware Gods here, so instead of forcing you into a "might panic" situation, it gives you the `Option` to handle a "This Should Never Happen" case.
2.`get_field` looks a little weird. I'm looking at the `Field::Read` part, specifically. `Field` is a type, and you need an instance of that type to pass to `get_field`. A cleaner API mightbe something like:
But remember that `Field` is a type synonym that has fixed indices for width, offset, etc. To be able to parameterize `get_field` like this, you'd need higher-kinded types.