Improving newtype

This commit is contained in:
Lokathor 2018-12-25 16:45:10 -07:00
parent 1a19831373
commit ce3ddd3bb0
2 changed files with 81 additions and 32 deletions

View file

@ -96,6 +96,11 @@ style, but there are some rules and considerations here:
* Parentheses macro use mostly gets treated like a function call. * Parentheses macro use mostly gets treated like a function call.
* Bracket macro use mostly gets treated like an array declaration. * Bracket macro use mostly gets treated like an array declaration.
**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.
## Upgrade That Macro! ## Upgrade That Macro!
We also want to be able to add `derive` stuff and doc comments to our newtype. We also want to be able to add `derive` stuff and doc comments to our newtype.
@ -124,34 +129,78 @@ newtype! {
} }
``` ```
And that's about all we'll need for the examples. Next, we can allow for the wrapping of types that aren't just a single
identifier by changing `$old_name` from `:ident` to `:ty`. We can't _also_ do
this for the `$new_type` part because declaring a new struct expects a valid
identifier that's _not_ already declared (obviously), and `:ty` is intended for
capturing types that already exist.
**As a reminder:** remember that `macro_rules` macros have to appear _before_ ```rust
they're invoked in your source, so the `newtype` macro will always have to be at #[macro_export]
the very top of your file, or if you put it in a module within your project macro_rules! newtype {
you'll need to declare the module before anything that uses it. ($(#[$attr:meta])* $new_name:ident, $old_name:ty) => {
$(#[$attr])*
#[repr(transparent)]
pub struct $new_name($old_name);
};
}
```
## Potential Homework Next of course we'll want to usually have a `new` method that's const and just
gives a 0 value. We won't always be making a newtype over a number value, but we
often will. It's usually silly to have a `new` method with no arguments since we
might as well just impl `Default`, but `Default::default` isn't `const`, so
having `pub const fn new() -> Self` is justified here.
If you wanted to keep going and get really fancy with it, you could potentially Here, the token `0` is given the `{integer}` type, which can be converted into
add a lot more: any of the integer types as needed, but it still can't be converted into an
array type or a pointer or things like that. Accordingly we've added the "no
frills" option which declares the struct and no `new` method.
* Make a `pub const fn new() -> Self` method that outputs the base value in a ```rust
const way. Combine this with builder style "setter" methods that are also #[macro_export]
const and you can get the compiler to do quite a bit of the value building macro_rules! newtype {
work at compile time. ($(#[$attr:meta])* $new_name:ident, $old_name:ty) => {
* Making the macro optionally emit a `From` impl to unwrap it back into the base $(#[$attr])*
type. #[repr(transparent)]
* Allow for visibility modifiers to be applied to the inner field and the newly pub struct $new_name($old_name);
generated type. impl $new_name {
* Allowing for generic newtypes. You already saw the need for this once in the /// A `const` "zero value" constructor
volatile section. Unfortunately, this particular part gets really tricky if pub const fn new() -> Self {
you're using `macro_rules!`, so you might need to move up to a full $new_name(0)
`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. ($(#[$attr:meta])* $new_name:ident, $old_name:ty, no frills) => {
* Allowing for optional `Deref` and `DerefMut` of the inner value. This takes $(#[$attr])*
away most all the safety aspect of doing the newtype, but there may be times #[repr(transparent)]
for it. As an example, you could make a newtype with a different form of pub struct $new_name($old_name);
Display impl that you want to otherwise treat as the base type in all places. };
}
```
Finally, we usually want to have the wrapped value be totally private, but there
_are_ occasions where that's not the case. For this, we can allow the wrapped
field to accept a visibility modifier.
```rust
#[macro_export]
macro_rules! newtype {
($(#[$attr:meta])* $new_name:ident, $v:vis $old_name:ty) => {
$(#[$attr])*
#[repr(transparent)]
pub struct $new_name($v $old_name);
impl $new_name {
/// A `const` "zero value" constructor
pub const fn new() -> Self {
$new_name(0)
}
}
};
($(#[$attr:meta])* $new_name:ident, $v:vis $old_name:ty, no frills) => {
$(#[$attr])*
#[repr(transparent)]
pub struct $new_name($v $old_name);
};
}
```

View file

@ -42,10 +42,10 @@ pub(crate) use gba_proc_macro::register_bit;
/// ``` /// ```
#[macro_export] #[macro_export]
macro_rules! newtype { macro_rules! newtype {
($(#[$attr:meta])* $new_name:ident, $old_name:ident) => { ($(#[$attr:meta])* $new_name:ident, $v:vis $old_name:ty) => {
$(#[$attr])* $(#[$attr])*
#[repr(transparent)] #[repr(transparent)]
pub struct $new_name($old_name); pub struct $new_name($v $old_name);
impl $new_name { impl $new_name {
/// A `const` "zero value" constructor /// A `const` "zero value" constructor
pub const fn new() -> Self { pub const fn new() -> Self {
@ -53,10 +53,10 @@ macro_rules! newtype {
} }
} }
}; };
($(#[$attr:meta])* $new_name:ident, $old_name:ident, no frills) => { ($(#[$attr:meta])* $new_name:ident, $v:vis $old_name:ty, no frills) => {
$(#[$attr])* $(#[$attr])*
#[repr(transparent)] #[repr(transparent)]
pub struct $new_name($old_name); pub struct $new_name($v $old_name);
}; };
} }
@ -65,8 +65,8 @@ pub(crate) use self::base::*;
pub mod bios; pub mod bios;
pub mod io; pub mod io;
pub mod mgba; pub mod mgba;
pub mod video;
pub mod palram; pub mod palram;
pub mod video;
newtype! { newtype! {
/// A color on the GBA is an RGB 5.5.5 within a `u16` /// A color on the GBA is an RGB 5.5.5 within a `u16`