Skip to content

Commit aac54c5

Browse files
committed
Merge pull request #873 from freebroccolo/master
Allow macros in types
2 parents 3ae29c6 + 82c85e4 commit aac54c5

File tree

1 file changed

+235
-0
lines changed

1 file changed

+235
-0
lines changed

text/0000-type-macros.md

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
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

Comments
 (0)