|
| 1 | +- Feature Name: Macros in type positions |
| 2 | +- Start Date: 2015-02-16 |
| 3 | +- RFC PR: (leave this empty) |
| 4 | +- Rust Issue: (leave this empty) |
| 5 | + |
| 6 | +# Summary |
| 7 | + |
| 8 | +Allow macros in type positions |
| 9 | + |
| 10 | +# Motivation |
| 11 | + |
| 12 | +Macros are currently allowed in syntax fragments for expressions, |
| 13 | +items, and patterns, but not for types. This RFC proposes to lift that |
| 14 | +restriction. |
| 15 | + |
| 16 | +1. This would allow macros to be used more flexibly, avoiding the |
| 17 | + need for more complex item-level macros or plugins in some |
| 18 | + cases. For example, when creating trait implementations with |
| 19 | + macros, it is sometimes useful to be able to define the |
| 20 | + associated types using a nested type macro but this is |
| 21 | + currently problematic. |
| 22 | + |
| 23 | +2. Enable more programming patterns, particularly with respect to |
| 24 | + type level programming. Macros in type positions provide |
| 25 | + convenient way to express recursion and choice. It is possible |
| 26 | + to do the same thing purely through programming with associated |
| 27 | + types but the resulting code can be cumbersome to read and write. |
| 28 | + |
| 29 | + |
| 30 | +# Detailed design |
| 31 | + |
| 32 | +## Implementation |
| 33 | + |
| 34 | +The proposed feature has been prototyped at |
| 35 | +[this branch](https://github.com/freebroccolo/rust/commits/feature/type_macros). The |
| 36 | +implementation is straightforward and the impact of the changes are |
| 37 | +limited in scope to the macro system. Type-checking and other phases |
| 38 | +of compilation should be unaffected. |
| 39 | + |
| 40 | +The most significant change introduced by this feature is a |
| 41 | +[`TyMac`](https://github.com/freebroccolo/rust/blob/f8f8dbb6d332c364ecf26b248ce5f872a7a67019/src/libsyntax/ast.rs#L1274-L1275) |
| 42 | +case for the `Ty_` enum so that the parser can indicate a macro |
| 43 | +invocation in a type position. In other words, `TyMac` is added to the |
| 44 | +ast and handled analogously to `ExprMac`, `ItemMac`, and `PatMac`. |
| 45 | + |
| 46 | +## Example: Heterogeneous Lists |
| 47 | + |
| 48 | +Heterogeneous lists are one example where the ability to express |
| 49 | +recursion via type macros is very useful. They can be used as an |
| 50 | +alternative to or in combination with tuples. Their recursive |
| 51 | +structure provide a means to abstract over arity and to manipulate |
| 52 | +arbitrary products of types with operations like appending, taking |
| 53 | +length, adding/removing items, computing permutations, etc. |
| 54 | + |
| 55 | +Heterogeneous lists can be defined like so: |
| 56 | + |
| 57 | +```rust |
| 58 | +#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] |
| 59 | +struct Nil; // empty HList |
| 60 | +#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] |
| 61 | +struct Cons<H, T: HList>(H, T); // cons cell of HList |
| 62 | + |
| 63 | +// trait to classify valid HLists |
| 64 | +trait HList: MarkerTrait {} |
| 65 | +impl HList for Nil {} |
| 66 | +impl<H, T: HList> HList for Cons<H, T> {} |
| 67 | +``` |
| 68 | + |
| 69 | +However, writing HList terms in code is not very convenient: |
| 70 | + |
| 71 | +```rust |
| 72 | +let xs = Cons("foo", Cons(false, Cons(vec![0u64], Nil))); |
| 73 | +``` |
| 74 | + |
| 75 | +At the term-level, this is an easy fix using macros: |
| 76 | + |
| 77 | +```rust |
| 78 | +// term-level macro for HLists |
| 79 | +macro_rules! hlist { |
| 80 | + {} => { Nil }; |
| 81 | + {=> $($elem:tt),+ } => { hlist_pat!($($elem),+) }; |
| 82 | + { $head:expr, $($tail:expr),* } => { Cons($head, hlist!($($tail),*)) }; |
| 83 | + { $head:expr } => { Cons($head, Nil) }; |
| 84 | +} |
| 85 | + |
| 86 | +// term-level HLists in patterns |
| 87 | +macro_rules! hlist_pat { |
| 88 | + {} => { Nil }; |
| 89 | + { $head:pat, $($tail:tt),* } => { Cons($head, hlist_pat!($($tail),*)) }; |
| 90 | + { $head:pat } => { Cons($head, Nil) }; |
| 91 | +} |
| 92 | + |
| 93 | +let xs = hlist!["foo", false, vec![0u64]]; |
| 94 | +``` |
| 95 | + |
| 96 | +Unfortunately, this solution is incomplete because we have only made |
| 97 | +HList terms easier to write. HList types are still inconvenient: |
| 98 | + |
| 99 | +```rust |
| 100 | +let xs: Cons<&str, Cons<bool, Cons<Vec<u64>, Nil>>> = hlist!["foo", false, vec![0u64]]; |
| 101 | +``` |
| 102 | + |
| 103 | +Allowing type macros as this RFC proposes would allows us to be |
| 104 | +able to use Rust's macros to improve writing the HList type as |
| 105 | +well. The complete example follows: |
| 106 | + |
| 107 | +```rust |
| 108 | +// term-level macro for HLists |
| 109 | +macro_rules! hlist { |
| 110 | + {} => { Nil }; |
| 111 | + {=> $($elem:tt),+ } => { hlist_pat!($($elem),+) }; |
| 112 | + { $head:expr, $($tail:expr),* } => { Cons($head, hlist!($($tail),*)) }; |
| 113 | + { $head:expr } => { Cons($head, Nil) }; |
| 114 | +} |
| 115 | + |
| 116 | +// term-level HLists in patterns |
| 117 | +macro_rules! hlist_pat { |
| 118 | + {} => { Nil }; |
| 119 | + { $head:pat, $($tail:tt),* } => { Cons($head, hlist_pat!($($tail),*)) }; |
| 120 | + { $head:pat } => { Cons($head, Nil) }; |
| 121 | +} |
| 122 | + |
| 123 | +// type-level macro for HLists |
| 124 | +macro_rules! HList { |
| 125 | + {} => { Nil }; |
| 126 | + { $head:ty } => { Cons<$head, Nil> }; |
| 127 | + { $head:ty, $($tail:ty),* } => { Cons<$head, HList!($($tail),*)> }; |
| 128 | +} |
| 129 | + |
| 130 | +let xs: HList![&str, bool, Vec<u64>] = hlist!["foo", false, vec![0u64]]; |
| 131 | +``` |
| 132 | + |
| 133 | +Operations on HLists can be defined by recursion, using traits with |
| 134 | +associated type outputs at the type-level and implementation methods |
| 135 | +at the term-level. |
| 136 | + |
| 137 | +The HList append operation is provided as an example. Type macros are |
| 138 | +used to make writing append at the type level (see `Expr!`) more |
| 139 | +convenient than specifying the associated type projection manually: |
| 140 | + |
| 141 | +```rust |
| 142 | +use std::ops; |
| 143 | + |
| 144 | +// nil case for HList append |
| 145 | +impl<Ys: HList> ops::Add<Ys> for Nil { |
| 146 | + type Output = Ys; |
| 147 | + |
| 148 | + fn add(self, rhs: Ys) -> Ys { |
| 149 | + rhs |
| 150 | + } |
| 151 | +} |
| 152 | + |
| 153 | +// cons case for HList append |
| 154 | +impl<Rec: HList + Sized, X, Xs: HList, Ys: HList> ops::Add<Ys> for Cons<X, Xs> where |
| 155 | + Xs: ops::Add<Ys, Output = Rec>, |
| 156 | +{ |
| 157 | + type Output = Cons<X, Rec>; |
| 158 | + |
| 159 | + fn add(self, rhs: Ys) -> Cons<X, Rec> { |
| 160 | + Cons(self.0, self.1 + rhs) |
| 161 | + } |
| 162 | +} |
| 163 | + |
| 164 | +// type macro Expr allows us to expand the + operator appropriately |
| 165 | +macro_rules! Expr { |
| 166 | + { ( $($LHS:tt)+ ) } => { Expr!($($LHS)+) }; |
| 167 | + { HList ! [ $($LHS:tt)* ] + $($RHS:tt)+ } => { <Expr!(HList![$($LHS)*]) as std::ops::Add<Expr!($($RHS)+)>>::Output }; |
| 168 | + { $LHS:tt + $($RHS:tt)+ } => { <Expr!($LHS) as std::ops::Add<Expr!($($RHS)+)>>::Output }; |
| 169 | + { $LHS:ty } => { $LHS }; |
| 170 | +} |
| 171 | + |
| 172 | +// test demonstrating term level `xs + ys` and type level `Expr!(Xs + Ys)` |
| 173 | +#[test] |
| 174 | +fn test_append() { |
| 175 | + fn aux<Xs: HList, Ys: HList>(xs: Xs, ys: Ys) -> Expr!(Xs + Ys) where |
| 176 | + Xs: ops::Add<Ys> |
| 177 | + { |
| 178 | + xs + ys |
| 179 | + } |
| 180 | + let xs: HList![&str, bool, Vec<u64>] = hlist!["foo", false, vec![]]; |
| 181 | + let ys: HList![u64, [u8; 3], ()] = hlist![0, [0, 1, 2], ()]; |
| 182 | + |
| 183 | + // demonstrate recursive expansion of Expr! |
| 184 | + let zs: Expr!((HList![&str] + HList![bool] + HList![Vec<u64>]) + |
| 185 | + (HList![u64] + HList![[u8; 3], ()]) + |
| 186 | + HList![]) |
| 187 | + = aux(xs, ys); |
| 188 | + assert_eq!(zs, hlist!["foo", false, vec![], 0, [0, 1, 2], ()]) |
| 189 | +} |
| 190 | +``` |
| 191 | + |
| 192 | +# Drawbacks |
| 193 | + |
| 194 | +There seem to be few drawbacks to implementing this feature as an |
| 195 | +extension of the existing macro machinery. The change adds a small |
| 196 | +amount of additional complexity to the |
| 197 | +[parser](https://github.com/freebroccolo/rust/commit/a224739e92a3aa1febb67d6371988622bd141361) |
| 198 | +and |
| 199 | +[conversion](https://github.com/freebroccolo/rust/commit/9341232087991dee73713dc4521acdce11a799a2) |
| 200 | +but the changes are minimal. |
| 201 | + |
| 202 | +As with all feature proposals, it is possible that designs for future |
| 203 | +extensions to the macro system or type system might interfere with |
| 204 | +this functionality but it seems unlikely unless they are significant, |
| 205 | +breaking changes. |
| 206 | + |
| 207 | +# Alternatives |
| 208 | + |
| 209 | +There are no _direct_ alternatives. Extensions to the type system like |
| 210 | +data kinds, singletons, and other forms of staged programming |
| 211 | +(so-called CTFE) might alleviate the need for type macros in some |
| 212 | +cases, however it is unlikely that they would provide a comprehensive |
| 213 | +replacement, particularly where plugins are concerned. |
| 214 | + |
| 215 | +Not implementing this feature would mean not taking some reasonably |
| 216 | +low-effort steps toward making certain programming patterns |
| 217 | +easier. One potential consequence of this might be more pressure to |
| 218 | +significantly extend the type system and other aspects of the language |
| 219 | +to compensate. |
| 220 | + |
| 221 | +# Unresolved questions |
| 222 | + |
| 223 | +## Alternative syntax for macro invocations in types |
| 224 | + |
| 225 | +There is a question as to whether type macros should allow `<` and `>` |
| 226 | +as delimiters for invocations, e.g. `Foo!<A>`. This would raise a |
| 227 | +number of additional complications and is probably not necessary to |
| 228 | +consider for this RFC. If deemed desirable by the community, this |
| 229 | +functionality should be proposed separately. |
| 230 | + |
| 231 | +## Hygiene and type macros |
| 232 | + |
| 233 | +This RFC also does not address the topic of hygiene regarding macros |
| 234 | +in types. It is not clear whether there are issues here or not but it |
| 235 | +may be worth considering in further detail. |
0 commit comments