# Mapping macros
To facilitate code that is generic over all variants of a `superstruct`, we generate several
_mapping macros_ with names like `map_foo!` and `map_foo_into_bar!`.
## Mapping into `Self`
For every top-level enum we generate a mapping macro that matches on values of
`Self` and is equipped with a variant constructor for `Self`.
Consider the following type:
```rust
#[superstruct(variants(First, Second)]
struct Foo {
x: u8,
#[only(Second)]
y: u8
}
```
The mapping macro for `Foo` will be:
```rust
macro_rules! map_foo {
($value:expr, $f:expr) => {
match $value {
Foo::First(inner) => f(inner, Foo::First),
Foo::Second(inner) => f(inner, Foo::Second),
}
}
}
```
i.e. `map_foo!` is a macro taking two arguments:
* `value`: an expression which must be of type `Foo`.
* `f`: a function expression, which takes two arguments `|inner, constructor|` where:
* `inner` is an instance of a variant struct, e.g. `FooFirst`. Note that
its type changes between branches!
* `constructor` is a function from the selected variant struct type to `Foo`. Its type
also changes between branches, and would be e.g. `fn(FooFirst) -> Foo` in the case
of the `First` branch.
Example usage looks like this:
```rust
impl Foo {
fn increase_x(self) -> Self {
map_foo!(self, |inner, constructor| {
inner.x += 1;
constructor(inner)
})
}
}
```
Although the type of `inner` could be `FooFirst` or `FooSecond`, both have an `x` field, so it is
legal to increment it. The `constructor` is then used to re-construct an instance of `Foo` by
injecting the updated `inner` value. If an invalid closure is provided then the type errors may
be quite opaque. On the other hand, if your code type-checks while using `map!` then you can rest
assured that it is valid (`superstruct` doesn't use any `unsafe` blocks or do any spicy casting).
> Tip: You don't need to use the constructor argument if you are implementing a straight-forward
> projection on `Self`. Although in some cases you may need to provide a type
> hint to the compiler, like `let _ = constructor(inner)`.
## Mapping from `Ref` and `RefMut`
Mapping macros for `Ref` and `RefMut` are also generated. They take an extra lifetime argument
(supplied as a reference to `_`) as their first argument, which must correspond to the lifetime
on the `Ref`/`RefMut` type.
Example usage for `Foo`:
```rust
impl Foo {
fn get_x<'a>(&'a self) -> &'a u64 {
map_foo_ref!(&'a _, self, |inner, _| {
&inner.x
})
}
}
```
## Mapping into other types
Mappings can also be generated between two `superstruct`s with identically named variants.
These mapping macros are available for the top-level enum, `Ref` and `RefMut`, and take the same
number of arguments. The only difference is that the constructor will be the constructor for the
type being mapped _into_.
The name of the mapping macro is `map_X_into_Y!` where `X` is the snake-cased
`Self` type and `Y` is the snake-cased target type.
Example:
```rust
#[superstruct(
variants(A, B),
variant_attributes(derive(Debug, PartialEq, Clone)),
map_into(Thing2),
map_ref_into(Thing2Ref),
map_ref_mut_into(Thing2RefMut)
)]
#[derive(Debug, PartialEq, Clone)]
pub struct Thing1 {
#[superstruct(only(A), partial_getter(rename = "thing2a"))]
thing2: Thing2A,
#[superstruct(only(B), partial_getter(rename = "thing2b"))]
thing2: Thing2B,
}
#[superstruct(variants(A, B), variant_attributes(derive(Debug, PartialEq, Clone)))]
#[derive(Debug, PartialEq, Clone)]
pub struct Thing2 {
x: u64,
}
fn thing1_to_thing2(thing1: Thing1) -> Thing2 {
map_thing1_into_thing2!(thing1, |inner, cons| { cons(inner.thing2) })
}
fn thing1_ref_to_thing2_ref<'a>(thing1: Thing1Ref<'a>) -> Thing2Ref<'a> {
map_thing1_ref_into_thing2_ref!(&'a _, thing1, |inner, cons| { cons(&inner.thing2) })
}
fn thing1_ref_mut_to_thing2_ref_mut<'a>(thing1: Thing1RefMut<'a>) -> Thing2RefMut<'a> {
map_thing1_ref_mut_into_thing2_ref_mut!(&'a _, thing1, |inner, cons| {
cons(&mut inner.thing2)
})
}
```
## Naming
Type names are converted from `CamelCase` to `snake_case` on a best-effort basis. E.g.
* `SignedBeaconBlock` -> `map_signed_beacon_block!`
* `NetworkDht` -> `map_network_dht!`
The current algorithm is quite simplistic and may produce strange names if it encounters
repeated capital letters. Please open an issue on GitHub if you have suggestions on how to
improve this!
## Limitations
* Presently only pure mapping functions are supported. The type-hinting hacks make it hard to
support proper closures.
* Sometimes type-hints are required, e.g. `let _ = constructor(inner)`.
* Macros are scoped per-module, so you need to be more mindful of name collisions than when
defining regular types.