2018-12-16 14:45:25 +11:00
|
|
|
# Constant Assertions
|
2018-12-16 14:35:57 +11:00
|
|
|
|
|
|
|
Have you ever wanted to assert things _even before runtime_? We all have, of
|
|
|
|
course. Particularly when the runtime machine is a poor little GBA, we'd like to
|
|
|
|
have the machine doing the compile handle as much checking as possible.
|
|
|
|
|
2018-12-16 14:45:25 +11:00
|
|
|
Enter the [static assertions](https://docs.rs/static_assertions/) crate, which
|
|
|
|
provides a way to let you assert on a `const` expression.
|
2018-12-16 14:35:57 +11:00
|
|
|
|
|
|
|
This is an amazing crate that you should definitely use when you can.
|
|
|
|
|
2018-12-16 14:47:00 +11:00
|
|
|
It's written by [Nikolai Vazquez](https://github.com/nvzqz), and they kindly
|
|
|
|
wrote up a [blog
|
2018-12-16 14:35:57 +11:00
|
|
|
post](https://nikolaivazquez.com/posts/programming/rust-static-assertions/) that
|
|
|
|
explains the thinking behind it.
|
|
|
|
|
|
|
|
However, I promised that each example would be single file, and I also promised
|
|
|
|
to explain what's going on as we go, so we'll briefly touch upon giving an
|
|
|
|
explanation here.
|
|
|
|
|
|
|
|
## How We Const Assert
|
|
|
|
|
|
|
|
Alright, as it stands (2018-12-15), we can't use `if` in a `const` context.
|
|
|
|
|
|
|
|
Since we can't use `if`, we can't use a normal `assert!`. Some day it will be
|
|
|
|
possible, and a failed assert at compile time will be a compile error and a
|
|
|
|
failed assert at run time will be a panic and we'll have a nice unified
|
|
|
|
programming experience. We can add runtime-only assertions by being a little
|
|
|
|
tricky with the compiler.
|
|
|
|
|
|
|
|
If we write
|
|
|
|
|
|
|
|
```rust
|
|
|
|
const ASSERT: usize = 0 - 1;
|
|
|
|
```
|
|
|
|
|
|
|
|
that gives a warning, since the math would underflow. We can upgrade that
|
|
|
|
warning to a hard error:
|
|
|
|
|
|
|
|
```rust
|
|
|
|
#[deny(const_err)]
|
|
|
|
const ASSERT: usize = 0 - 1;
|
|
|
|
```
|
|
|
|
|
2018-12-16 14:58:53 +11:00
|
|
|
And to make our construction reusable we can enable the
|
|
|
|
[underscore_const_names](https://github.com/rust-lang/rust/issues/54912) feature
|
|
|
|
in our program (or library) and then give each such const an underscore for a
|
2018-12-16 14:35:57 +11:00
|
|
|
name.
|
|
|
|
|
|
|
|
```rust
|
|
|
|
#![feature(underscore_const_names)]
|
|
|
|
|
|
|
|
#[deny(const_err)]
|
|
|
|
const _: usize = 0 - 1;
|
|
|
|
```
|
|
|
|
|
2018-12-16 14:43:01 +11:00
|
|
|
Now we wrap this in a macro where we give a `bool` expression as input. We
|
|
|
|
negate the bool then cast it to a `usize`, meaning that `true` negates into
|
|
|
|
`false`, which becomes `0usize`, and then there's no underflow error. Or if the
|
|
|
|
input was `false`, it negates into `true`, then becomes `1usize`, and then the
|
|
|
|
underflow error fires.
|
2018-12-16 14:35:57 +11:00
|
|
|
|
|
|
|
```rust
|
|
|
|
macro_rules! const_assert {
|
|
|
|
($condition:expr) => {
|
|
|
|
#[deny(const_err)]
|
|
|
|
#[allow(dead_code)]
|
|
|
|
const ASSERT: usize = 0 - !$condition as usize;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2018-12-16 14:43:01 +11:00
|
|
|
Technically, written like this, the expression can be anything with a
|
|
|
|
`core::ops::Not` implementation that can also be `as` cast into `usize`. That's
|
2018-12-16 15:11:13 +11:00
|
|
|
`bool`, but also basically all the other number types. Since we want to ensure
|
|
|
|
that we get proper looking type errors when things go wrong, we can use
|
|
|
|
`($condition && true)` to enforce that we get a `bool` (thanks to `Talchas` for
|
|
|
|
that particular suggestion).
|
|
|
|
|
|
|
|
```rust
|
|
|
|
macro_rules! const_assert {
|
|
|
|
($condition:expr) => {
|
|
|
|
#[deny(const_err)]
|
|
|
|
#[allow(dead_code)]
|
|
|
|
const _: usize = 0 - !($condition && true) as usize;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
2018-12-16 14:35:57 +11:00
|
|
|
|
|
|
|
## Asserting Something
|
|
|
|
|
|
|
|
As an example of how we might use a `const_assert`, we'll do a demo with colors.
|
|
|
|
There's a red, blue, and green channel. We store colors in a `u16` with 5 bits
|
|
|
|
for each channel.
|
|
|
|
|
|
|
|
```rust
|
|
|
|
newtype! {
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
|
Color, u16
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
And when we're building a color, we're passing in `u16` values, but they could
|
|
|
|
be using more than just 5 bits of space. We want to make sure that each channel
|
|
|
|
is 31 or less, so we can make a color builder that does a `const_assert!` on the
|
|
|
|
value of each channel.
|
|
|
|
|
|
|
|
```rust
|
|
|
|
macro_rules! rgb {
|
|
|
|
($r:expr, $g:expr, $b:expr) => {
|
|
|
|
{
|
|
|
|
const_assert!($r <= 31);
|
|
|
|
const_assert!($g <= 31);
|
|
|
|
const_assert!($b <= 31);
|
|
|
|
Color($b << 10 | $g << 5 | $r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
And then we can declare some colors
|
|
|
|
|
|
|
|
```rust
|
|
|
|
const RED: Color = rgb!(31, 0, 0);
|
|
|
|
|
|
|
|
const BLUE: Color = rgb!(31, 500, 0);
|
|
|
|
```
|
|
|
|
|
|
|
|
The second one is clearly out of bounds and it fires an error just like we
|
|
|
|
wanted.
|