mirror of
https://github.com/italicsjenga/gba.git
synced 2025-01-11 11:31:31 +11:00
static asserts
This commit is contained in:
parent
7cdcc02aaf
commit
51d3915dea
|
@ -40,19 +40,6 @@ with our newtypes.
|
|||
pub struct PixelColor(u16);
|
||||
```
|
||||
|
||||
Ah, and of course we'll need to make it so you can unwrap the value:
|
||||
|
||||
```rust
|
||||
#[repr(transparent)]
|
||||
pub struct PixelColor(u16);
|
||||
|
||||
impl From<PixelColor> for u16 {
|
||||
fn from(color: PixelColor) -> u16 {
|
||||
color.0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And then we'll need to do that same thing for _every other newtype we want_.
|
||||
|
||||
Except there's only two tiny parts that actually differ between newtype
|
||||
|
@ -62,7 +49,12 @@ a job for a macro to me!
|
|||
|
||||
## Making It A Macro
|
||||
|
||||
The most basic version of the macro we want goes like this:
|
||||
If you're going to do much with macros you should definitely read through [The
|
||||
Little Book of Rust
|
||||
Macros](https://danielkeep.github.io/tlborm/book/index.html), but we won't be
|
||||
doing too much so you can just follow along here a bit if you like.
|
||||
|
||||
The most basic version of a newtype macro starts like this:
|
||||
|
||||
```rust
|
||||
#[macro_export]
|
||||
|
@ -74,8 +66,39 @@ macro_rules! newtype {
|
|||
}
|
||||
```
|
||||
|
||||
Except we also want to be able to add attributes (which includes doc comments),
|
||||
so we upgrade our macro a bit:
|
||||
The `#[macro_export]` makes it exported by the current module (like `pub`
|
||||
kinda), and then we have one expansion option that takes an identifier, a `,`,
|
||||
and then a second identifier. The new name is the outer type we'll be using, and
|
||||
the old name is the inner type that's being wrapped. You'd use our new macro
|
||||
something like this:
|
||||
|
||||
```rust
|
||||
newtype! {PixelColorCurly, u16}
|
||||
|
||||
newtype!(PixelColorParens, u16);
|
||||
|
||||
newtype![PixelColorBrackets, u16];
|
||||
```
|
||||
|
||||
Note that you can invoke the macro with the outermost grouping as any of `()`,
|
||||
`[]`, or `{}`. It makes no particular difference to the macro. Also, that space
|
||||
in the first version is kinda to show off that you can put white space in
|
||||
between the macro name and the grouping if you want. The difference is mostly
|
||||
style, but there are some rules and considerations here:
|
||||
|
||||
* If you use curly braces then you _must not_ put a `;` after the invocation.
|
||||
* If you use parentheses or brackets then you _must_ put the `;` at the end.
|
||||
* Rustfmt cares which you use and formats accordingly:
|
||||
* Curly brace macro use mostly gets treated like a code block.
|
||||
* Parentheses macro use mostly gets treated like a function call.
|
||||
* Bracket macro use mostly gets treated like an array declaration.
|
||||
|
||||
## Upgrade That Macro!
|
||||
|
||||
We also want to be able to add `derive` stuff and doc comments to our newtype.
|
||||
Within the context of `macro_rules!` definitions these are called "meta". Since
|
||||
we can have any number of them we wrap it all up in a "zero or more" matcher.
|
||||
Then our macro looks like this:
|
||||
|
||||
```rust
|
||||
#[macro_export]
|
||||
|
@ -88,52 +111,44 @@ macro_rules! newtype {
|
|||
}
|
||||
```
|
||||
|
||||
And we want to automatically add the ability to turn the wrapper type back into
|
||||
the wrapped type.
|
||||
So now we can write
|
||||
|
||||
```rust
|
||||
#[macro_export]
|
||||
macro_rules! newtype {
|
||||
($(#[$attr:meta])* $new_name:ident, $old_name:ident) => {
|
||||
$(#[$attr])*
|
||||
#[repr(transparent)]
|
||||
pub struct $new_name($old_name);
|
||||
|
||||
impl From<$new_name> for $old_name {
|
||||
fn from(x: $new_name) -> $old_name {
|
||||
x.0
|
||||
}
|
||||
}
|
||||
};
|
||||
newtype! {
|
||||
/// Color on the GBA gives 5 bits for each channel, the highest bit is ignored.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
PixelColor, u16
|
||||
}
|
||||
```
|
||||
|
||||
That seems like enough for all of our examples, so we'll stop there. We could
|
||||
add more things:
|
||||
|
||||
* Making the `From` impl being optional. We'd have to make the newtype
|
||||
invocation be more complicated somehow, the user puts ", no-unwrap" after the
|
||||
inner type declaration or something, or something like that.
|
||||
* Allowing for more precise visibility controls on the wrapping type and on the
|
||||
inner field. This would add a lot of line noise, so we'll just always have our
|
||||
newtypes be `pub`.
|
||||
* Allowing for generic newtypes, which might sound silly but that we'll actually
|
||||
see an example of soon enough. To do this you might _think_ that we can change
|
||||
the `:ident` declarations to `:ty`, but since we're declaring a fresh type not
|
||||
using an existing type we have to accept it as an `:ident`. The way you get
|
||||
around this is with a proc-macro, which is a lot more powerful but which also
|
||||
requires that you write the proc-macro in an entirely other crate that gets
|
||||
compiled first. We don't need that much power, so for our examples we'll go
|
||||
with the macro_rules version and just do it by hand in the few cases where we
|
||||
need a generic newtype.
|
||||
* Allowing for `Deref` and `DerefMut`, which usually defeats the point of doing
|
||||
the newtype, but maybe sometimes it's the right thing, so if you were going
|
||||
for the full industrial strength version with a proc-macro and all you might
|
||||
want to make that part of your optional add-ons as well the same way you might
|
||||
want optional `From`. You'd probably want `From` to be "on by default" and
|
||||
`Deref`/`DerefMut` to be "off by default", but whatever.
|
||||
And that's about all we'll need for the examples.
|
||||
|
||||
**As a reminder:** remember that `macro_rules` macros have to appear _before_
|
||||
they're invoked in your source, so the `newtype` macro will always have to be at
|
||||
the very top of your file, or if you put it in a module within your project
|
||||
you'll need to declare the module before anything that uses it.
|
||||
|
||||
## Potential Homework
|
||||
|
||||
If you wanted to keep going and get really fancy with it, you could potentially
|
||||
add a lot more:
|
||||
|
||||
* Make a `pub const fn new() -> Self` method that outputs the base value in a
|
||||
const way. Combine this with builder style "setter" methods that are also
|
||||
const and you can get the compiler to do quite a bit of the value building
|
||||
work at compile time.
|
||||
* Making the macro optionally emit a `From` impl to unwrap it back into the base
|
||||
type.
|
||||
* Allow for visibility modifiers to be applied to the inner field and the newly
|
||||
generated type.
|
||||
* Allowing for generic newtypes. You already saw the need for this once in the
|
||||
volatile section. Unfortunately, this particular part gets really tricky if
|
||||
you're using `macro_rules!`, so you might need to move up to a full
|
||||
`proc_macro`. Having a `proc_macro` isn't bad except that they have to be
|
||||
defined in a crate of their own and they're compiled before use. You can't
|
||||
ever use them in the crate that defines them, so we won't be using them in any
|
||||
of our single file examples.
|
||||
* Allowing for optional `Deref` and `DerefMut` of the inner value. This takes
|
||||
away most all the safety aspect of doing the newtype, but there may be times
|
||||
for it. As an example, you could make a newtype with a different form of
|
||||
Display impl that you want to otherwise treat as the base type in all places.
|
||||
|
|
114
book/src/01-quirks/05-static_asserts.md
Normal file
114
book/src/01-quirks/05-static_asserts.md
Normal file
|
@ -0,0 +1,114 @@
|
|||
# Static Asserts
|
||||
|
||||
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.
|
||||
|
||||
Enter [static assertions](https://docs.rs/static_assertions/).
|
||||
|
||||
This is an amazing crate that you should definitely use when you can.
|
||||
|
||||
It's written by [nvzqz](https://github.com/nvzqz), and they kindly wrote up a
|
||||
[blog
|
||||
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;
|
||||
```
|
||||
|
||||
And to make our construction reusable we can enable the `underscore_const_names`
|
||||
feature in our program or library and give each such const an underscore for a
|
||||
name.
|
||||
|
||||
```rust
|
||||
#![feature(underscore_const_names)]
|
||||
|
||||
#[deny(const_err)]
|
||||
const _: usize = 0 - 1;
|
||||
```
|
||||
|
||||
Now we wrap this in a macro where we give an expression for a bool. 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.
|
||||
|
||||
```rust
|
||||
macro_rules! const_assert {
|
||||
($condition:expr) => {
|
||||
#[deny(const_err)]
|
||||
#[allow(dead_code)]
|
||||
const ASSERT: usize = 0 - !$condition as usize;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This allows anything which supports `core::ops::Not` and can then can cast into
|
||||
`usize`, which technically isn't just `bool` values, but close enough.
|
||||
|
||||
## 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.
|
|
@ -12,6 +12,7 @@
|
|||
* [Fixed Only](01-quirks/02-fixed_only.md)
|
||||
* [Volatile Destination](01-quirks/03-volatile_destination.md)
|
||||
* [Newtype](01-quirks/04-newtype.md)
|
||||
* [Static Asserts](01-quirks/05-static_asserts.md)
|
||||
* [Concepts](02-concepts/00-index.md)
|
||||
* [CPU](02-concepts/01-cpu.md)
|
||||
* [BIOS](02-concepts/02-bios.md)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#![feature(start)]
|
||||
#![no_std]
|
||||
#![feature(start)]
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||
|
|
|
@ -1,18 +1,65 @@
|
|||
#![feature(start)]
|
||||
#![no_std]
|
||||
#![feature(start)]
|
||||
#![feature(underscore_const_names)]
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! newtype {
|
||||
($(#[$attr:meta])* $new_name:ident, $old_name:ident) => {
|
||||
$(#[$attr])*
|
||||
#[repr(transparent)]
|
||||
pub struct $new_name($old_name);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! const_assert {
|
||||
($condition:expr) => {
|
||||
#[deny(const_err)]
|
||||
#[allow(dead_code)]
|
||||
const _: usize = 0 - !$condition as usize;
|
||||
};
|
||||
}
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
newtype! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
Color, u16
|
||||
}
|
||||
|
||||
pub const fn rgb(red: u16, green: u16, blue: u16) -> Color {
|
||||
Color(blue << 10 | green << 5 | red)
|
||||
}
|
||||
|
||||
newtype! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
DisplayControlSetting, u16
|
||||
}
|
||||
|
||||
pub const DISPLAY_CONTROL: VolatilePtr<DisplayControlSetting> = VolatilePtr(0x04000000 as *mut DisplayControlSetting);
|
||||
pub const JUST_MODE3_AND_BG2: DisplayControlSetting = DisplayControlSetting(3 + 0b100_0000_0000);
|
||||
|
||||
pub struct Mode3;
|
||||
|
||||
impl Mode3 {
|
||||
const SCREEN_WIDTH: isize = 240;
|
||||
const PIXELS: VolatilePtr<Color> = VolatilePtr(0x600_0000 as *mut Color);
|
||||
|
||||
pub unsafe fn draw_pixel_unchecked(col: isize, row: isize, color: Color) {
|
||||
Self::PIXELS.offset(col + row * Self::SCREEN_WIDTH).write(color);
|
||||
}
|
||||
}
|
||||
|
||||
#[start]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
unsafe {
|
||||
DISPCNT.write(MODE3 | BG2);
|
||||
mode3_pixel(120, 80, rgb16(31, 0, 0));
|
||||
mode3_pixel(136, 80, rgb16(0, 31, 0));
|
||||
mode3_pixel(120, 96, rgb16(0, 0, 31));
|
||||
DISPLAY_CONTROL.write(JUST_MODE3_AND_BG2);
|
||||
Mode3::draw_pixel_unchecked(120, 80, rgb(31, 0, 0));
|
||||
Mode3::draw_pixel_unchecked(136, 80, rgb(0, 31, 0));
|
||||
Mode3::draw_pixel_unchecked(120, 96, rgb(0, 0, 31));
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
@ -31,18 +78,3 @@ impl<T> VolatilePtr<T> {
|
|||
VolatilePtr(self.0.wrapping_offset(count))
|
||||
}
|
||||
}
|
||||
|
||||
pub const DISPCNT: VolatilePtr<u16> = VolatilePtr(0x04000000 as *mut u16);
|
||||
pub const MODE3: u16 = 3;
|
||||
pub const BG2: u16 = 0b100_0000_0000;
|
||||
|
||||
pub const VRAM: usize = 0x06000000;
|
||||
pub const SCREEN_WIDTH: isize = 240;
|
||||
|
||||
pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 {
|
||||
blue << 10 | green << 5 | red
|
||||
}
|
||||
|
||||
pub unsafe fn mode3_pixel(col: isize, row: isize, color: u16) {
|
||||
VolatilePtr(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write(color);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#![feature(start)]
|
||||
#![no_std]
|
||||
#![feature(start)]
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||
|
|
Loading…
Reference in a new issue