|
| 1 | +# Mapping macros |
| 2 | + |
| 3 | +To facilitate code that is generic over all variants of a `superstruct`, we generate several |
| 4 | +_mapping macros_ with names like `map_foo!` and `map_foo_into_bar!`. |
| 5 | + |
| 6 | +## Mapping into `Self` |
| 7 | + |
| 8 | +For every top-level enum we generate a mapping macro that matches on values of |
| 9 | +`Self` and is equipped with a variant constructor for `Self`. |
| 10 | + |
| 11 | +Consider the following type: |
| 12 | + |
| 13 | +```rust |
| 14 | +#[superstruct(variants(First, Second)] |
| 15 | +struct Foo { |
| 16 | + x: u8, |
| 17 | + #[only(Second)] |
| 18 | + y: u8 |
| 19 | +} |
| 20 | +``` |
| 21 | + |
| 22 | +The mapping macro for `Foo` will be: |
| 23 | + |
| 24 | +```rust |
| 25 | +macro_rules! map_foo { |
| 26 | + ($value:expr, $f:expr) => { |
| 27 | + match $value { |
| 28 | + Foo::First(inner) => f(inner, Foo::First), |
| 29 | + Foo::Second(inner) => f(inner, Foo::Second), |
| 30 | + } |
| 31 | + } |
| 32 | +} |
| 33 | +``` |
| 34 | + |
| 35 | +i.e. `map_foo!` is a macro taking two arguments: |
| 36 | + |
| 37 | +* `value`: an expression which must be of type `Foo`. |
| 38 | +* `f`: a function expression, which takes two arguments `|inner, constructor|` where: |
| 39 | + * `inner` is an instance of a variant struct, e.g. `FooFirst`. Note that |
| 40 | + its type changes between branches! |
| 41 | + * `constructor` is a function from the selected variant struct type to `Foo`. Its type |
| 42 | + also changes between branches, and would be e.g. `fn(FooFirst) -> Foo` in the case |
| 43 | + of the `First` branch. |
| 44 | + |
| 45 | +Example usage looks like this: |
| 46 | + |
| 47 | +```rust |
| 48 | +impl Foo { |
| 49 | + fn increase_x(self) -> Self { |
| 50 | + map_foo!(self, |inner, constructor| { |
| 51 | + inner.x += 1; |
| 52 | + constructor(inner) |
| 53 | + }) |
| 54 | + } |
| 55 | +} |
| 56 | +``` |
| 57 | + |
| 58 | +Although the type of `inner` could be `FooFirst` or `FooSecond`, both have an `x` field, so it is |
| 59 | +legal to increment it. The `constructor` is then used to re-construct an instance of `Foo` by |
| 60 | +injecting the updated `inner` value. If an invalid closure is provided then the type errors may |
| 61 | +be quite opaque. On the other hand, if your code type-checks while using `map!` then you can rest |
| 62 | +assured that it is valid (`superstruct` doesn't use any `unsafe` blocks or do any spicy casting). |
| 63 | + |
| 64 | +> Tip: You don't need to use the constructor argument if you are implementing a straight-forward |
| 65 | +> projection on `Self`. Although in some cases you may need to provide a type |
| 66 | +> hint to the compiler, like `let _ = constructor(inner)`. |
| 67 | +
|
| 68 | +## Mapping from `Ref` and `RefMut` |
| 69 | + |
| 70 | +Mapping macros for `Ref` and `RefMut` are also generated. They take an extra lifetime argument |
| 71 | +(supplied as a reference to `_`) as their first argument, which must correspond to the lifetime |
| 72 | +on the `Ref`/`RefMut` type. |
| 73 | + |
| 74 | +Example usage for `Foo`: |
| 75 | + |
| 76 | +```rust |
| 77 | +impl Foo { |
| 78 | + fn get_x<'a>(&'a self) -> &'a u64 { |
| 79 | + map_foo_ref!(&'a _, self, |inner, _| { |
| 80 | + &inner.x |
| 81 | + }) |
| 82 | + } |
| 83 | +} |
| 84 | +``` |
| 85 | + |
| 86 | +## Mapping into other types |
| 87 | + |
| 88 | +Mappings can also be generated between two `superstruct`s with identically named variants. |
| 89 | + |
| 90 | +These mapping macros are available for the top-level enum, `Ref` and `RefMut`, and take the same |
| 91 | +number of arguments. The only difference is that the constructor will be the constructor for the |
| 92 | +type being mapped _into_. |
| 93 | + |
| 94 | +The name of the mapping macro is `map_X_into_Y!` where `X` is the snake-cased |
| 95 | +`Self` type and `Y` is the snake-cased target type. |
| 96 | + |
| 97 | +Example: |
| 98 | + |
| 99 | +```rust |
| 100 | +#[superstruct( |
| 101 | + variants(A, B), |
| 102 | + variant_attributes(derive(Debug, PartialEq, Clone)), |
| 103 | + map_into(Thing2), |
| 104 | + map_ref_into(Thing2Ref), |
| 105 | + map_ref_mut_into(Thing2RefMut) |
| 106 | +)] |
| 107 | +#[derive(Debug, PartialEq, Clone)] |
| 108 | +pub struct Thing1 { |
| 109 | + #[superstruct(only(A), partial_getter(rename = "thing2a"))] |
| 110 | + thing2: Thing2A, |
| 111 | + #[superstruct(only(B), partial_getter(rename = "thing2b"))] |
| 112 | + thing2: Thing2B, |
| 113 | +} |
| 114 | + |
| 115 | +#[superstruct(variants(A, B), variant_attributes(derive(Debug, PartialEq, Clone)))] |
| 116 | +#[derive(Debug, PartialEq, Clone)] |
| 117 | +pub struct Thing2 { |
| 118 | + x: u64, |
| 119 | +} |
| 120 | + |
| 121 | +fn thing1_to_thing2(thing1: Thing1) -> Thing2 { |
| 122 | + map_thing1_into_thing2!(thing1, |inner, cons| { cons(inner.thing2) }) |
| 123 | +} |
| 124 | + |
| 125 | +fn thing1_ref_to_thing2_ref<'a>(thing1: Thing1Ref<'a>) -> Thing2Ref<'a> { |
| 126 | + map_thing1_ref_into_thing2_ref!(&'a _, thing1, |inner, cons| { cons(&inner.thing2) }) |
| 127 | +} |
| 128 | + |
| 129 | +fn thing1_ref_mut_to_thing2_ref_mut<'a>(thing1: Thing1RefMut<'a>) -> Thing2RefMut<'a> { |
| 130 | + map_thing1_ref_mut_into_thing2_ref_mut!(&'a _, thing1, |inner, cons| { |
| 131 | + cons(&mut inner.thing2) |
| 132 | + }) |
| 133 | +} |
| 134 | +``` |
| 135 | + |
| 136 | +## Naming |
| 137 | + |
| 138 | +Type names are converted from `CamelCase` to `snake_case` on a best-effort basis. E.g. |
| 139 | + |
| 140 | +* `SignedBeaconBlock` -> `map_signed_beacon_block!` |
| 141 | +* `NetworkDht` -> `map_network_dht!` |
| 142 | + |
| 143 | +The current algorithm is quite simplistic and may produce strange names if it encounters |
| 144 | +repeated capital letters. Please open an issue on GitHub if you have suggestions on how to |
| 145 | +improve this! |
| 146 | + |
| 147 | +## Limitations |
| 148 | + |
| 149 | +* Presently only pure mapping functions are supported. The type-hinting hacks make it hard to |
| 150 | + support proper closures. |
| 151 | +* Sometimes type-hints are required, e.g. `let _ = constructor(inner)`. |
| 152 | +* Macros are scoped per-module, so you need to be more mindful of name collisions than when |
| 153 | + defining regular types. |
0 commit comments