From cd368cd3b9210be5f1841c3debc76f981b2ad22f Mon Sep 17 00:00:00 2001 From: Darin Morrison Date: Mon, 16 Feb 2015 13:10:31 -0700 Subject: [PATCH 01/18] Allow macros in types --- text/0000-type-macros.md | 412 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 412 insertions(+) create mode 100644 text/0000-type-macros.md diff --git a/text/0000-type-macros.md b/text/0000-type-macros.md new file mode 100644 index 00000000000..290c2265b9d --- /dev/null +++ b/text/0000-type-macros.md @@ -0,0 +1,412 @@ +- Feature Name: Macros in type positions +- Start Date: 2015-02-16 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary + +Allow macros in type positions + +# Motivation + +Macros are currently allowed in syntax fragments for expressions, +items, and patterns, but not for types. This RFC proposes to lift that +restriction for the following reasons: + +1. Increase generality of the macro system - in the absence of a + concrete reason for disallowing macros in types, the limitation + should be removed in order to promote generality and to enable use + cases which would otherwise require resorting either to compiler + plugins or to more elaborate item-level macros. + +2. Enable more programming patterns - macros in type positions provide + a means to express **recursion** and **choice** within types in a + fashion that is still legible. Associated types alone can accomplish + the former (recursion/choice) but not the latter (legibility). + +# Detailed design + +## Implementation + +The proposed feature has been implemented at +[this branch](https://github.com/freebroccolo/rust/commits/feature/type_macros). There +is no real novelty to the design as it is simply an extension of the +existing macro machinery to handle the additional case of macro +expansion in types. The biggest change is the addition of a +[`TyMac`](https://github.com/freebroccolo/rust/blob/f8f8dbb6d332c364ecf26b248ce5f872a7a67019/src/libsyntax/ast.rs#L1274-L1275) +to the `Ty_` enum so that the parser can indicate a macro invocation +in a type position. In other words, `TyMac` is added to the ast and +handled analogously to `ExprMac`, `ItemMac`, and `PatMac`. + +## Examples + +### Heterogeneous Lists + +Heterogeneous lists are one example where the ability to express +recursion via type macros is very useful. They can be used as an +alternative to (or in combination with) tuples. Their recursive +structure provide a means to abstract over arity and to manipulate +arbitrary products of types with operations like appending, taking +length, adding/removing items, computing permutations, etc. + +Heterogeneous lists are straightforward to define: + +```rust +struct Nil; // empty HList +struct Cons(H, T); // cons cell of HList + +// trait to classify valid HLists +trait HList {} +impl HList for Nil {} +impl HList for Cons {} +``` + +However, writing them in code is not so convenient: + +```rust +let xs = Cons("foo", Cons(false, Cons(vec![0u64], Nil))); +``` + +At the term-level, this is easy enough to fix with a macro: + +```rust +// term-level macro for HLists +macro_rules! hlist { + {} => { Nil }; + { $head:expr } => { Cons($head, Nil) }; + { $head:expr, $($tail:expr),* } => { Cons($head, hlist!($($tail),*)) }; +} + +let xs = hlist!["foo", false, vec![0u64]]; +``` + +Unfortunately, this is an incomplete solution. HList terms are more +convenient to write but HList types are not: + +```rust +let xs: Cons<&str, Cons, Nil>>> = hlist!["foo", false, vec![0u64]]; +``` + +Under this proposal—allowing macros in types—we would be able to use a +macro to improve writing the HList type as well. The complete example +follows: + +```rust +// term-level macro for HLists +macro_rules! hlist { + {} => { Nil }; + { $head:expr } => { Cons($head, Nil) }; + { $head:expr, $($tail:expr),* } => { Cons($head, hlist!($($tail),*)) }; +} + +// type-level macro for HLists +macro_rules! HList { + {} => { Nil }; + { $head:ty } => { Cons<$head, Nil> }; + { $head:ty, $($tail:ty),* } => { Cons<$head, HList!($($tail),*)> }; +} + +let xs: HList![&str, bool, Vec] = hlist!["foo", false, vec![0u64]]; +``` + +Operations on HLists can be defined by recursion, using traits with +associated type outputs at the type-level and implementation methods +at the term-level. + +HList append is provided as an example of such an operation. Macros in +types are used to make writing append at the type level more +convenient, e.g., with `Expr!`: + +```rust +use std::ops; + +// nil case for HList append +impl ops::Add for Nil { + type Output = Ys; + + #[inline] + fn add(self, rhs: Ys) -> Ys { + rhs + } +} + +// cons case for HList append +impl ops::Add for Cons where + Xs: ops::Add, +{ + type Output = Cons; + + #[inline] + fn add(self, rhs: Ys) -> Cons { + Cons(self.0, self.1 + rhs) + } +} + +// type macro Expr allows us to expand the + operator appropriately +macro_rules! Expr { + { $A:ty } => { $A }; + { $LHS:tt + $RHS:tt } => { >::Output }; +} + +// test demonstrating term level `xs + ys` and type level `Expr!(Xs + Ys)` +#[test] +fn test_append() { + fn aux(xs: Xs, ys: Ys) -> Expr!(Xs + Ys) where + Xs: ops::Add + { + xs + ys + } + let xs: HList![&str, bool, Vec] = hlist!["foo", false, vec![]]; + let ys: HList![u64, [u8; 3], ()] = hlist![0, [0, 1, 2], ()]; + + // parentheses around compound types due to limitations in macro parsing; + // real implementation could use a plugin to avoid this + let zs: Expr!((HList![&str, bool, Vec]) + + (HList![u64, [u8; 3], ()])) + = aux(xs, ys); + assert_eq!(zs, hlist!["foo", false, vec![], 0, [0, 1, 2], ()]) +} +``` + +### Additional Examples ### + +#### Type-level numbers + +Another example where type macros can be useful is in the encoding of +numbers as types. Binary natural numbers can be represented as +follows: + +```rust +struct _0; // 0 bit +struct _1; // 1 bit + +// classify valid bits +trait Bit {} +impl Bit for _0 {} +impl Bit for _1 {} + +// classify positive binary naturals +trait Pos {} +impl Pos for _1 {} +impl Pos for (P, B) {} + +// classify binary naturals with 0 +trait Nat {} +impl Nat for _0 {} +impl Nat for _1 {} +impl Nat for (P, B) {} +``` + +These can be used to index into tuples or HLists generically (linear +time generally or constant time up to a fixed number of +specializations). They can also be used to encode "sized" or "bounded" +data, like vectors: + +```rust +struct LengthVec(Vec); +``` + +The type number can either be a phantom parameter `N` as above, or +represented concretely at the term-level (similar to list). In either +case, a length-safe API can be provided on top of types `Vec`. Because +the length is known statically, unsafe indexing would be allowable by +default. + +We could imagine an idealized API in the following fashion: + +```rust +// push, adding one to the length +fn push(x: A, xs: LengthVec) -> LengthVec; + +// pop, subtracting one from the length +fn pop(store: &mut A, xs: LengthVec) -> LengthVec; + +// append, adding the individual lengths +fn append(xs: LengthVec, ys: LengthVec) -> LengthVec; + +// produce a length respecting iterator from an indexed vector +fn iter(xs: LengthVec) -> LengthIterator; +``` + +However, in order to be able to write something close to that in Rust, +we would need macros in types: + +```rust + +// Nat! would expand integer constants to type-level binary naturals; would +// be implemented as a plugin for efficiency +Nat!(4) ==> ((_1, _0), _0) + +// Expr! would expand + to Add::Output and integer constants to Nat!; see +// the HList append earlier in the RFC for a concrete example of how this +// might be defined +Expr!(N + M) ==> >::Output + +// Now we could expand the following type to something meaningful in Rust: +LengthVec + ==> LengthVec>::Output> + ==> LengthVec>::Output> +``` + +##### Optimization of `Expr`! + +Because `Expr!` could be implemented as a plugin, the opportunity +would exist to perform various optimizations of type-level expressions +during expansion. Partial evaluation would be one approach to +this. Furthermore, expansion-time optimizations would not necessarily +be limited to simple arithmetic expressions but could be used for +other data like HLists. + +#### Conversion from HList to Tuple + +With type macros, it is possible to write macros that convert back and +forth between tuples and HLists in the following fashion: + +```rust +// type-level macro for HLists +macro_rules! HList { + {} => { Nil }; + { $head:ty } => { Cons<$head, Nil> }; + { $head:ty, $($tail:ty),* } => { Cons<$head, HList!($($tail),*)> }; +} + +// term-level macro for HLists +macro_rules! hlist { + {} => { Nil }; + { $head:expr } => { Cons($head, Nil) }; + { $head:expr, $($tail:expr),* } => { Cons($head, hlist!($($tail),*)) }; +} + +// term-level HLists in patterns +macro_rules! hlist_match { + {} => { Nil }; + { $head:ident } => { Cons($head, Nil) }; + { $head:ident, $($tail:ident),* } => { Cons($head, hlist_match!($($tail),*)) }; +} + +// iterate macro for generated comma separated sequences of idents +fn impl_for_seq_upto_expand<'cx>( + ecx: &'cx mut base::ExtCtxt, + span: codemap::Span, + args: &[ast::TokenTree], +) -> Box { + let mut parser = ecx.new_parser_from_tts(args); + + // parse the macro name + let mac = parser.parse_ident(); + + // parse a comma + parser.eat(&token::Token::Comma); + + // parse the number of iterations + let iterations = match parser.parse_lit().node { + ast::Lit_::LitInt(i, _) => i, + _ => { + ecx.span_err(span, "welp"); + return base::DummyResult::any(span); + } + }; + + // generate a token tree: A0, ..., An + let mut ctx = range(0, iterations * 2 - 1).flat_map(|k| { + if k % 2 == 0 { + token::str_to_ident(format!("A{}", (k / 2)).as_slice()) + .to_tokens(ecx) + .into_iter() + } else { + let span = codemap::DUMMY_SP; + let token = parse::token::Token::Comma; + vec![ast::TokenTree::TtToken(span, token)] + .into_iter() + } + }).collect::>(); + + // iterate over the ctx and generate impl syntax fragments + let mut items = vec![]; + let mut i = ctx.len(); + for _ in range(0, iterations) { + items.push(quote_item!(ecx, $mac!{ $ctx };).unwrap()); + i -= 2; + ctx.truncate(i); + } + + // splice the impl fragments into the ast + base::MacItems::new(items.into_iter()) +} + +pub struct ToHList; +pub struct ToTuple; + +// macro to implement: ToTuple(hlist![…]) => (…,) +macro_rules! impl_to_tuple_for_seq { + ($($seq:ident),*) => { + #[allow(non_snake_case)] + impl<$($seq,)*> Fn<(HList![$($seq),*],)> for ToTuple { + type Output = ($($seq,)*); + #[inline] + extern "rust-call" fn call(&self, (this,): (HList![$($seq),*],)) -> ($($seq,)*) { + match this { + hlist_match![$($seq),*] => ($($seq,)*) + } + } + } + } +} + +// macro to implement: ToHList((…,)) => hlist![…] +macro_rules! impl_to_hlist_for_seq { + ($($seq:ident),*) => { + #[allow(non_snake_case)] + impl<$($seq,)*> Fn<(($($seq,)*),)> for ToHList { + type Output = HList![$($seq),*]; + #[inline] + extern "rust-call" fn call(&self, (this,): (($($seq,)*),)) -> HList![$($seq),*] { + match this { + ($($seq,)*) => hlist![$($seq),*] + } + } + } + } +} + +// generate implementations up to length 32 +impl_for_seq_upto!{ impl_to_tuple_for_seq, 32 } +impl_for_seq_upto!{ impl_to_hlist_for_seq, 32 } +``` + +# Drawbacks + +There seem to be few drawbacks to implementing this feature as an +extension of the existing macro machinery. Parsing macro invocations +in types adds a very small amount of additional complexity to the +parser (basically looking for `!`). Having an extra case for macro +invocation in types slightly complicates conversion. As with all +feature proposals, it is possible that designs for future extensions +to the macro system or type system might somehow interfere with this +functionality. + +# Alternatives + +There are no direct alternatives to my knowledge. Extensions to the +type system like data kinds, singletons, and various more elaborate +forms of staged programming (so-called CTFE) could conceivably cover +some cases where macros in types might otherwise be used. It is +unlikely they would provide the same level of functionality as macros, +particularly where plugins are concerned. Instead, such features would +probably benefit from type macros too. + +Not implementing this feature would mean disallowing some useful +programming patterns. There are some discussions in the community +regarding more extensive changes to the type system to address some of +these patterns. However, type macros along with associated types can +already accomplish many of the same things without the significant +engineering cost in terms of changes to the type system. Either way, +type macros would not prevent additional extensions. + +# Unresolved questions + +There is a question as to whether macros in types should allow `<` and +`>` as delimiters for invocations, e.g. `Foo!`. However, this would +raise a number of additional complications and is not necessary to +consider for this RFC. If deemed desirable by the community, this +functionality can be proposed separately. From 4782cd831aaf7e69fa05444b528855d0c85ed0fd Mon Sep 17 00:00:00 2001 From: Darin Morrison Date: Wed, 18 Feb 2015 10:33:29 -0700 Subject: [PATCH 02/18] Link RFC for parameterizing types with constants --- text/0000-type-macros.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/text/0000-type-macros.md b/text/0000-type-macros.md index 290c2265b9d..14738dea32a 100644 --- a/text/0000-type-macros.md +++ b/text/0000-type-macros.md @@ -257,6 +257,17 @@ this. Furthermore, expansion-time optimizations would not necessarily be limited to simple arithmetic expressions but could be used for other data like HLists. +##### Native alternatives: types parameterized by constant values + +This example with type-level naturals is meant to illustrate the kind +of patterns macros in types enable. I am not suggesting the standard +libraries adopt _this particular_ representation as a means to address +the more general issue of lack of numeric parameterization for +types. There is +[another RFC here](https://github.com/rust-lang/rfcs/pull/884) which +does propose extending the type system to allow parameterization over +constants. + #### Conversion from HList to Tuple With type macros, it is possible to write macros that convert back and From e40ea65e9bb378081a7a2cd73378f48012729ce2 Mon Sep 17 00:00:00 2001 From: Darin Morrison Date: Wed, 18 Feb 2015 10:43:12 -0700 Subject: [PATCH 03/18] Add tests to hlist/tuple conversion example --- text/0000-type-macros.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/text/0000-type-macros.md b/text/0000-type-macros.md index 14738dea32a..478733f5095 100644 --- a/text/0000-type-macros.md +++ b/text/0000-type-macros.md @@ -383,6 +383,20 @@ macro_rules! impl_to_hlist_for_seq { // generate implementations up to length 32 impl_for_seq_upto!{ impl_to_tuple_for_seq, 32 } impl_for_seq_upto!{ impl_to_hlist_for_seq, 32 } + +// test converting an hlist to tuple +#[test] +fn test_to_tuple() { + assert_eq(ToTuple(hlist!["foo", true, (), vec![42u64]]), + ("foo", true, (), vec![42u64])) +} + +// test converting a tuple to hlist +#[test] +fn test_to_hlist() { + assert_eq(ToHList(("foo", true, (), vec![42u64])), + hlist!["foo", true, (), vec![42u64]]) +} ``` # Drawbacks From 6afbb395257d910b2b4d6376fbc42d37b6b90ade Mon Sep 17 00:00:00 2001 From: Darin Morrison Date: Wed, 18 Feb 2015 20:06:39 -0700 Subject: [PATCH 04/18] Rewording, comments, etc. --- text/0000-type-macros.md | 76 +++++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 25 deletions(-) diff --git a/text/0000-type-macros.md b/text/0000-type-macros.md index 478733f5095..f29c7e1ecc0 100644 --- a/text/0000-type-macros.md +++ b/text/0000-type-macros.md @@ -29,14 +29,18 @@ restriction for the following reasons: ## Implementation The proposed feature has been implemented at -[this branch](https://github.com/freebroccolo/rust/commits/feature/type_macros). There -is no real novelty to the design as it is simply an extension of the -existing macro machinery to handle the additional case of macro -expansion in types. The biggest change is the addition of a +[this branch](https://github.com/freebroccolo/rust/commits/feature/type_macros). The +implementation is very simple and there is no novelty to the +design. The patches make a small modification to the existing macro +expansion functionality in order to support macro invocations in +syntax for types. No changes are made to type-checking or other phases +of the compiler. + +The biggest change introduced by this feature is a [`TyMac`](https://github.com/freebroccolo/rust/blob/f8f8dbb6d332c364ecf26b248ce5f872a7a67019/src/libsyntax/ast.rs#L1274-L1275) -to the `Ty_` enum so that the parser can indicate a macro invocation -in a type position. In other words, `TyMac` is added to the ast and -handled analogously to `ExprMac`, `ItemMac`, and `PatMac`. +case for the `Ty_` enum so that the parser can indicate a macro +invocation in a type position. In other words, `TyMac` is added to the +ast and handled analogously to `ExprMac`, `ItemMac`, and `PatMac`. ## Examples @@ -235,12 +239,14 @@ we would need macros in types: // Nat! would expand integer constants to type-level binary naturals; would // be implemented as a plugin for efficiency -Nat!(4) ==> ((_1, _0), _0) +Nat!(4) + ==> ((_1, _0), _0) // Expr! would expand + to Add::Output and integer constants to Nat!; see // the HList append earlier in the RFC for a concrete example of how this // might be defined -Expr!(N + M) ==> >::Output +Expr!(N + M) + ==> >::Output // Now we could expand the following type to something meaningful in Rust: LengthVec @@ -271,7 +277,12 @@ constants. #### Conversion from HList to Tuple With type macros, it is possible to write macros that convert back and -forth between tuples and HLists in the following fashion: +forth between tuples and HLists. This is very powerful because it lets +us reuse all of the operations we define for HLists (appending, taking +length, adding/removing items, computing permutations, etc.) on tuples +just by converting to HList, computing, then convert back to a tuple. + +The conversion can be implemented in the following fashion: ```rust // type-level macro for HLists @@ -295,8 +306,17 @@ macro_rules! hlist_match { { $head:ident, $($tail:ident),* } => { Cons($head, hlist_match!($($tail),*)) }; } -// iterate macro for generated comma separated sequences of idents -fn impl_for_seq_upto_expand<'cx>( +// `invoke_for_seq_upto` is a `higher-order` macro that takes the name +// of another macro and a number and iteratively invokes the named +// macro with sequences of identifiers, e.g., +// +// invoke_for_seq_upto{ my_mac, 5 } +// ==> my_mac!{ A0, A1, A2, A3, A4 }; +// my_mac!{ A0, A1, A2, A3 }; +// my_mac!{ A0, A1, A2 }; +// ... + +fn invoke_for_seq_upto_expand<'cx>( ecx: &'cx mut base::ExtCtxt, span: codemap::Span, args: &[ast::TokenTree], @@ -348,8 +368,9 @@ fn impl_for_seq_upto_expand<'cx>( pub struct ToHList; pub struct ToTuple; -// macro to implement: ToTuple(hlist![…]) => (…,) -macro_rules! impl_to_tuple_for_seq { +// macro to implement conversion from hlist to tuple, +// e.g., ToTuple(hlist![…]) ==> (…,) +macro_rules! impl_to_tuple { ($($seq:ident),*) => { #[allow(non_snake_case)] impl<$($seq,)*> Fn<(HList![$($seq),*],)> for ToTuple { @@ -364,8 +385,9 @@ macro_rules! impl_to_tuple_for_seq { } } -// macro to implement: ToHList((…,)) => hlist![…] -macro_rules! impl_to_hlist_for_seq { +// macro to implement conversion from tuple to hlist, +// e.g., ToHList((…,)) ==> hlist![…] +macro_rules! impl_to_hlist { ($($seq:ident),*) => { #[allow(non_snake_case)] impl<$($seq,)*> Fn<(($($seq,)*),)> for ToHList { @@ -381,8 +403,8 @@ macro_rules! impl_to_hlist_for_seq { } // generate implementations up to length 32 -impl_for_seq_upto!{ impl_to_tuple_for_seq, 32 } -impl_for_seq_upto!{ impl_to_hlist_for_seq, 32 } +invoke_for_seq_upto!{ impl_to_tuple, 32 } +invoke_for_seq_upto!{ impl_to_hlist, 32 } // test converting an hlist to tuple #[test] @@ -402,13 +424,17 @@ fn test_to_hlist() { # Drawbacks There seem to be few drawbacks to implementing this feature as an -extension of the existing macro machinery. Parsing macro invocations -in types adds a very small amount of additional complexity to the -parser (basically looking for `!`). Having an extra case for macro -invocation in types slightly complicates conversion. As with all -feature proposals, it is possible that designs for future extensions -to the macro system or type system might somehow interfere with this -functionality. +extension of the existing macro machinery. The change adds a very +small amount of additional complexity to the +[parser](https://github.com/freebroccolo/rust/blob/e09cb32bcc04029dc4c16790e2aaa9811af27f25/src/libsyntax/parse/parser.rs#L1547-L1560) +and +[conversion](https://github.com/freebroccolo/rust/blob/e4b826b7afa1b5496b41ddaa1666014046ac5704/src/librustc_typeck/astconv.rs#L1301-L1303) +but the changes are almost negligible. + +As with all feature proposals, it is possible that designs for future +extensions to the macro system or type system might somehow interfere +with this functionality but it seems unlikely unless they are +significant, breaking changes. # Alternatives From 5985668d68480fa724f2301edcd2d68a96a7255f Mon Sep 17 00:00:00 2001 From: Darin Morrison Date: Fri, 20 Feb 2015 03:56:59 -0700 Subject: [PATCH 05/18] Cleanup invoke_for_seq_upto macro --- text/0000-type-macros.md | 68 +++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/text/0000-type-macros.md b/text/0000-type-macros.md index f29c7e1ecc0..6842868d944 100644 --- a/text/0000-type-macros.md +++ b/text/0000-type-macros.md @@ -315,7 +315,6 @@ macro_rules! hlist_match { // my_mac!{ A0, A1, A2, A3 }; // my_mac!{ A0, A1, A2 }; // ... - fn invoke_for_seq_upto_expand<'cx>( ecx: &'cx mut base::ExtCtxt, span: codemap::Span, @@ -327,42 +326,45 @@ fn invoke_for_seq_upto_expand<'cx>( let mac = parser.parse_ident(); // parse a comma - parser.eat(&token::Token::Comma); + parser.expect(&token::Token::Comma); // parse the number of iterations - let iterations = match parser.parse_lit().node { - ast::Lit_::LitInt(i, _) => i, - _ => { - ecx.span_err(span, "welp"); - return base::DummyResult::any(span); - } - }; - - // generate a token tree: A0, ..., An - let mut ctx = range(0, iterations * 2 - 1).flat_map(|k| { - if k % 2 == 0 { - token::str_to_ident(format!("A{}", (k / 2)).as_slice()) - .to_tokens(ecx) - .into_iter() - } else { - let span = codemap::DUMMY_SP; - let token = parse::token::Token::Comma; - vec![ast::TokenTree::TtToken(span, token)] - .into_iter() + if let ast::Lit_::LitInt(lit, _) = parser.parse_lit().node { + Some(lit) + } else { + None + }.and_then(|iterations| { + + // generate a token tree: A0, ..., An + let mut ctx = range(0, iterations * 2 - 1).flat_map(|k| { + if k % 2 == 0 { + token::str_to_ident(format!("A{}", (k / 2)).as_slice()) + .to_tokens(ecx) + .into_iter() + } else { + let span = codemap::DUMMY_SP; + let token = parse::token::Token::Comma; + vec![ast::TokenTree::TtToken(span, token)] + .into_iter() + } + }).collect::>(); + + // iterate over the ctx and generate impl syntax fragments + let mut items = vec![]; + let mut i = ctx.len(); + for _ in range(0, iterations) { + items.push(quote_item!(ecx, $mac!{ $ctx };).unwrap()); + i -= 2; + ctx.truncate(i); } - }).collect::>(); - - // iterate over the ctx and generate impl syntax fragments - let mut items = vec![]; - let mut i = ctx.len(); - for _ in range(0, iterations) { - items.push(quote_item!(ecx, $mac!{ $ctx };).unwrap()); - i -= 2; - ctx.truncate(i); - } - // splice the impl fragments into the ast - base::MacItems::new(items.into_iter()) + // splice the impl fragments into the ast + Some(base::MacItems::new(items.into_iter())) + + }).unwrap_or_else(|| { + ecx.span_err(span, "invoke_for_seq_upto!: expected an integer literal argument"); + base::DummyResult::any(span) + }) } pub struct ToHList; From d9b15ed4283fa98337231d492bd680b7132053cc Mon Sep 17 00:00:00 2001 From: Darin Morrison Date: Fri, 20 Feb 2015 04:03:18 -0700 Subject: [PATCH 06/18] Add example plugin to expand integers to type nats --- text/0000-type-macros.md | 93 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 87 insertions(+), 6 deletions(-) diff --git a/text/0000-type-macros.md b/text/0000-type-macros.md index 6842868d944..214efe87fe5 100644 --- a/text/0000-type-macros.md +++ b/text/0000-type-macros.md @@ -236,24 +236,105 @@ However, in order to be able to write something close to that in Rust, we would need macros in types: ```rust - -// Nat! would expand integer constants to type-level binary naturals; would -// be implemented as a plugin for efficiency -Nat!(4) - ==> ((_1, _0), _0) - // Expr! would expand + to Add::Output and integer constants to Nat!; see // the HList append earlier in the RFC for a concrete example of how this // might be defined Expr!(N + M) ==> >::Output +// Nat! would expand integer literals to type-level binary naturals +// and be implemented as a plugin for efficiency; see the following +// section for a concrete implementation +Nat!(4) + ==> ((_1, _0), _0) + // Now we could expand the following type to something meaningful in Rust: LengthVec ==> LengthVec>::Output> ==> LengthVec>::Output> ``` +##### Implementation of `Nat!` as a plugin + +The following code demonstrates concretely how `Nat!` can be +implemented as a plugin. As with the `HList!` example, this code is +already usable with the type macros implemented in the branch +referenced earlier in this RFC. + +For efficiency, the binary representation is first constructed as a +string via iteration rather than recursively using `quote` macros. The +string is then parsed as a type, returning an ast fragment. + +```rust +// convert a u64 to a string representation of a type-level binary natural, e.g., +// to_bin_nat(1024) +// ==> (((((((((_1, _0), _0), _0), _0), _0), _0), _0), _0), _0) +#[inline] +fn to_bin_nat(mut num: u64) -> String { + let mut res = String::from_str("_"); + if num < 2 { + res.push_str(num.to_string().as_slice()); + } else { + let mut bin = vec![]; + while num > 0 { + bin.push(num % 2); + num >>= 1; + } + res = ::std::iter::repeat('(').take(bin.len() - 1).collect(); + res.push_str("_"); + res.push_str(bin.pop().unwrap().to_string().as_slice()); + for b in bin.iter().rev() { + res.push_str(", _"); + res.push_str(b.to_string().as_slice()); + res.push_str(")"); + } + } + return res; +} + +// generate a parser to convert a string representation of a type-level natural +// to an ast fragment for a type +#[inline] +pub fn bin_nat_parser<'cx>( + ecx: &'cx mut base::ExtCtxt, + num: u64, +) -> parse::parser::Parser<'cx> { + let filemap = ecx + .codemap() + .new_filemap(String::from_str(""), to_bin_nat(num)); + let reader = lexer::StringReader::new( + &ecx.parse_sess().span_diagnostic, + filemap); + parser::Parser::new( + ecx.parse_sess(), + ecx.cfg(), + Box::new(reader)) +} + +// Expand Nat!(n) to a type-level binary nat where n is an int literal, e.g., +// Nat!(1024) +// ==> (((((((((_1, _0), _0), _0), _0), _0), _0), _0), _0), _0) +#[inline] +pub fn nat_expand<'cx>( + ecx: &'cx mut base::ExtCtxt, + span: codemap::Span, + args: &[ast::TokenTree], +) -> Box { + let mut litp = ecx.new_parser_from_tts(args); + if let ast::Lit_::LitInt(lit, _) = litp.parse_lit().node { + Some(lit) + } else { + None + }.and_then(|lit| { + let mut natp = bin_nat_parser(ecx, lit); + Some(base::MacTy::new(natp.parse_ty())) + }).unwrap_or_else(|| { + ecx.span_err(span, "Nat!: expected an integer literal argument"); + base::DummyResult::any(span) + }) +} +``` + ##### Optimization of `Expr`! Because `Expr!` could be implemented as a plugin, the opportunity From 508ece03c7eaa12ada8b74e4b7c09356a637eba4 Mon Sep 17 00:00:00 2001 From: Darin Morrison Date: Fri, 20 Feb 2015 13:56:42 -0700 Subject: [PATCH 07/18] Remove unnecessary attributes from examples --- text/0000-type-macros.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/text/0000-type-macros.md b/text/0000-type-macros.md index 214efe87fe5..d5e6ef88c45 100644 --- a/text/0000-type-macros.md +++ b/text/0000-type-macros.md @@ -128,7 +128,6 @@ use std::ops; impl ops::Add for Nil { type Output = Ys; - #[inline] fn add(self, rhs: Ys) -> Ys { rhs } @@ -140,7 +139,6 @@ impl ops::Add for Cons w { type Output = Cons; - #[inline] fn add(self, rhs: Ys) -> Cons { Cons(self.0, self.1 + rhs) } @@ -269,7 +267,6 @@ string is then parsed as a type, returning an ast fragment. // convert a u64 to a string representation of a type-level binary natural, e.g., // to_bin_nat(1024) // ==> (((((((((_1, _0), _0), _0), _0), _0), _0), _0), _0), _0) -#[inline] fn to_bin_nat(mut num: u64) -> String { let mut res = String::from_str("_"); if num < 2 { @@ -294,7 +291,6 @@ fn to_bin_nat(mut num: u64) -> String { // generate a parser to convert a string representation of a type-level natural // to an ast fragment for a type -#[inline] pub fn bin_nat_parser<'cx>( ecx: &'cx mut base::ExtCtxt, num: u64, @@ -314,7 +310,6 @@ pub fn bin_nat_parser<'cx>( // Expand Nat!(n) to a type-level binary nat where n is an int literal, e.g., // Nat!(1024) // ==> (((((((((_1, _0), _0), _0), _0), _0), _0), _0), _0), _0) -#[inline] pub fn nat_expand<'cx>( ecx: &'cx mut base::ExtCtxt, span: codemap::Span, @@ -458,7 +453,6 @@ macro_rules! impl_to_tuple { #[allow(non_snake_case)] impl<$($seq,)*> Fn<(HList![$($seq),*],)> for ToTuple { type Output = ($($seq,)*); - #[inline] extern "rust-call" fn call(&self, (this,): (HList![$($seq),*],)) -> ($($seq,)*) { match this { hlist_match![$($seq),*] => ($($seq,)*) @@ -475,7 +469,6 @@ macro_rules! impl_to_hlist { #[allow(non_snake_case)] impl<$($seq,)*> Fn<(($($seq,)*),)> for ToHList { type Output = HList![$($seq),*]; - #[inline] extern "rust-call" fn call(&self, (this,): (($($seq,)*),)) -> HList![$($seq),*] { match this { ($($seq,)*) => hlist![$($seq),*] From f448514b4db4c5e48ce06447b94db40959646781 Mon Sep 17 00:00:00 2001 From: Darin Morrison Date: Fri, 20 Feb 2015 14:00:50 -0700 Subject: [PATCH 08/18] Clean up nat plugin example; add term-level macro --- text/0000-type-macros.md | 69 ++++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/text/0000-type-macros.md b/text/0000-type-macros.md index d5e6ef88c45..6208bf4d25b 100644 --- a/text/0000-type-macros.md +++ b/text/0000-type-macros.md @@ -265,10 +265,11 @@ string is then parsed as a type, returning an ast fragment. ```rust // convert a u64 to a string representation of a type-level binary natural, e.g., -// to_bin_nat(1024) +// nat_str(1024) // ==> (((((((((_1, _0), _0), _0), _0), _0), _0), _0), _0), _0) -fn to_bin_nat(mut num: u64) -> String { - let mut res = String::from_str("_"); +fn nat_str(mut num: u64) -> String { + let path = "bit::_"; + let mut res = String::from_str(path); if num < 2 { res.push_str(num.to_string().as_slice()); } else { @@ -278,10 +279,11 @@ fn to_bin_nat(mut num: u64) -> String { num >>= 1; } res = ::std::iter::repeat('(').take(bin.len() - 1).collect(); - res.push_str("_"); + res.push_str(path); res.push_str(bin.pop().unwrap().to_string().as_slice()); for b in bin.iter().rev() { - res.push_str(", _"); + res.push_str(", "); + res.push_str(path); res.push_str(b.to_string().as_slice()); res.push_str(")"); } @@ -289,15 +291,14 @@ fn to_bin_nat(mut num: u64) -> String { return res; } -// generate a parser to convert a string representation of a type-level natural -// to an ast fragment for a type -pub fn bin_nat_parser<'cx>( +// Generate a parser with the nat string for `num` as input +fn nat_str_parser<'cx>( ecx: &'cx mut base::ExtCtxt, num: u64, ) -> parse::parser::Parser<'cx> { let filemap = ecx .codemap() - .new_filemap(String::from_str(""), to_bin_nat(num)); + .new_filemap(String::from_str(""), nat_str(num)); let reader = lexer::StringReader::new( &ecx.parse_sess().span_diagnostic, filemap); @@ -307,27 +308,61 @@ pub fn bin_nat_parser<'cx>( Box::new(reader)) } +// Try to parse an integer literal and return a new parser for its nat +// string; this is used to create both a type-level `Nat!` with +// `nat_ty_expand` and term-level `nat!` macro with `nat_tm_expand` +pub fn nat_lit_parser<'cx>( + ecx: &'cx mut base::ExtCtxt, + args: &[ast::TokenTree], +) -> Option> { + let mut litp = ecx.new_parser_from_tts(args); + if let ast::Lit_::LitInt(lit, _) = litp.parse_lit().node { + Some(nat_str_parser(ecx, lit)) + } else { + None + } +} + // Expand Nat!(n) to a type-level binary nat where n is an int literal, e.g., // Nat!(1024) // ==> (((((((((_1, _0), _0), _0), _0), _0), _0), _0), _0), _0) -pub fn nat_expand<'cx>( +pub fn nat_ty_expand<'cx>( ecx: &'cx mut base::ExtCtxt, span: codemap::Span, args: &[ast::TokenTree], ) -> Box { - let mut litp = ecx.new_parser_from_tts(args); - if let ast::Lit_::LitInt(lit, _) = litp.parse_lit().node { - Some(lit) - } else { - None - }.and_then(|lit| { - let mut natp = bin_nat_parser(ecx, lit); + { + nat_lit_parser(ecx, args) + }.and_then(|mut natp| { Some(base::MacTy::new(natp.parse_ty())) }).unwrap_or_else(|| { ecx.span_err(span, "Nat!: expected an integer literal argument"); base::DummyResult::any(span) }) } + +// Expand nat!(n) to a term-level binary nat where n is an int literal, e.g., +// nat!(1024) +// ==> (((((((((_1, _0), _0), _0), _0), _0), _0), _0), _0), _0) +pub fn nat_tm_expand<'cx>( + ecx: &'cx mut base::ExtCtxt, + span: codemap::Span, + args: &[ast::TokenTree], +) -> Box { + { + nat_lit_parser(ecx, args) + }.and_then(|mut natp| { + Some(base::MacExpr::new(natp.parse_expr())) + }).unwrap_or_else(|| { + ecx.span_err(span, "nat!: expected an integer literal argument"); + base::DummyResult::any(span) + }) +} + +#[test] +fn nats() { + let _: Nat!(42) = nat!(42); +} ``` ##### Optimization of `Expr`! From 41673515e458eb98c64e390a536d9f0c5af7b6f8 Mon Sep 17 00:00:00 2001 From: Darin Morrison Date: Fri, 20 Feb 2015 18:22:04 -0700 Subject: [PATCH 09/18] More clean up; mention hygiene --- text/0000-type-macros.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/text/0000-type-macros.md b/text/0000-type-macros.md index 6208bf4d25b..4b963a76633 100644 --- a/text/0000-type-macros.md +++ b/text/0000-type-macros.md @@ -268,10 +268,9 @@ string is then parsed as a type, returning an ast fragment. // nat_str(1024) // ==> (((((((((_1, _0), _0), _0), _0), _0), _0), _0), _0), _0) fn nat_str(mut num: u64) -> String { - let path = "bit::_"; - let mut res = String::from_str(path); + let mut res: String; if num < 2 { - res.push_str(num.to_string().as_slice()); + res = num.to_string(); } else { let mut bin = vec![]; while num > 0 { @@ -279,11 +278,9 @@ fn nat_str(mut num: u64) -> String { num >>= 1; } res = ::std::iter::repeat('(').take(bin.len() - 1).collect(); - res.push_str(path); res.push_str(bin.pop().unwrap().to_string().as_slice()); for b in bin.iter().rev() { res.push_str(", "); - res.push_str(path); res.push_str(b.to_string().as_slice()); res.push_str(")"); } @@ -567,8 +564,16 @@ type macros would not prevent additional extensions. # Unresolved questions +## Alternative syntax for macro invocations in types + There is a question as to whether macros in types should allow `<` and `>` as delimiters for invocations, e.g. `Foo!`. However, this would raise a number of additional complications and is not necessary to consider for this RFC. If deemed desirable by the community, this functionality can be proposed separately. + +## Hygiene and type macros + +This RFC does not address the topic of hygiene regarding macros in +types. It is not clear to me whether there are issues here or not but +it may be worth considering in further detail. From f7d65de2ce0ff70474f29057cbe814146c6550ac Mon Sep 17 00:00:00 2001 From: Darin Morrison Date: Fri, 20 Feb 2015 20:35:17 -0700 Subject: [PATCH 10/18] Rewording; clarification; cleanup --- text/0000-type-macros.md | 204 ++++++++++++++++++++------------------- 1 file changed, 106 insertions(+), 98 deletions(-) diff --git a/text/0000-type-macros.md b/text/0000-type-macros.md index 4b963a76633..b0249df0cce 100644 --- a/text/0000-type-macros.md +++ b/text/0000-type-macros.md @@ -13,11 +13,10 @@ Macros are currently allowed in syntax fragments for expressions, items, and patterns, but not for types. This RFC proposes to lift that restriction for the following reasons: -1. Increase generality of the macro system - in the absence of a - concrete reason for disallowing macros in types, the limitation - should be removed in order to promote generality and to enable use - cases which would otherwise require resorting either to compiler - plugins or to more elaborate item-level macros. +1. Increase generality of the macro system - the limitation should be + removed in order to promote generality and to enable use cases which + would otherwise require resorting either more elaborate plugins or + macros at the item-level. 2. Enable more programming patterns - macros in type positions provide a means to express **recursion** and **choice** within types in a @@ -28,15 +27,13 @@ restriction for the following reasons: ## Implementation -The proposed feature has been implemented at +The proposed feature has been prototyped at [this branch](https://github.com/freebroccolo/rust/commits/feature/type_macros). The -implementation is very simple and there is no novelty to the -design. The patches make a small modification to the existing macro -expansion functionality in order to support macro invocations in -syntax for types. No changes are made to type-checking or other phases -of the compiler. +implementation is straightforward and the impact of the changes are +limited in scope to the macro system. Type-checking and other phases +of compilation should be unaffected. -The biggest change introduced by this feature is a +The most significant change introduced by this feature is a [`TyMac`](https://github.com/freebroccolo/rust/blob/f8f8dbb6d332c364ecf26b248ce5f872a7a67019/src/libsyntax/ast.rs#L1274-L1275) case for the `Ty_` enum so that the parser can indicate a macro invocation in a type position. In other words, `TyMac` is added to the @@ -48,12 +45,12 @@ ast and handled analogously to `ExprMac`, `ItemMac`, and `PatMac`. Heterogeneous lists are one example where the ability to express recursion via type macros is very useful. They can be used as an -alternative to (or in combination with) tuples. Their recursive +alternative to or in combination with tuples. Their recursive structure provide a means to abstract over arity and to manipulate arbitrary products of types with operations like appending, taking length, adding/removing items, computing permutations, etc. -Heterogeneous lists are straightforward to define: +Heterogeneous lists can be defined like so: ```rust struct Nil; // empty HList @@ -65,13 +62,13 @@ impl HList for Nil {} impl HList for Cons {} ``` -However, writing them in code is not so convenient: +However, writing HList terms in code is not very convenient: ```rust let xs = Cons("foo", Cons(false, Cons(vec![0u64], Nil))); ``` -At the term-level, this is easy enough to fix with a macro: +At the term-level, this is an easy fix using macros: ```rust // term-level macro for HLists @@ -84,16 +81,16 @@ macro_rules! hlist { let xs = hlist!["foo", false, vec![0u64]]; ``` -Unfortunately, this is an incomplete solution. HList terms are more -convenient to write but HList types are not: +Unfortunately, this solution is incomplete because we have only made +HList terms easier to write. HList types are still inconvenient: ```rust let xs: Cons<&str, Cons, Nil>>> = hlist!["foo", false, vec![0u64]]; ``` -Under this proposal—allowing macros in types—we would be able to use a -macro to improve writing the HList type as well. The complete example -follows: +Allowing type macros as this RFC proposes would allows us to be +able to use Rust's macros to improve writing the HList type as +well. The complete example follows: ```rust // term-level macro for HLists @@ -117,9 +114,9 @@ Operations on HLists can be defined by recursion, using traits with associated type outputs at the type-level and implementation methods at the term-level. -HList append is provided as an example of such an operation. Macros in -types are used to make writing append at the type level more -convenient, e.g., with `Expr!`: +The HList append operation is provided as an example. type macros are +used to make writing append at the type level (see `Expr!`) more +convenient than specifying the associated type projection manually: ```rust use std::ops; @@ -172,11 +169,12 @@ fn test_append() { ### Additional Examples ### -#### Type-level numbers +#### Type-level numerics -Another example where type macros can be useful is in the encoding of -numbers as types. Binary natural numbers can be represented as -follows: +Type-level numerics are another area where type macros can be +useful. The more common unary encodings (Peano numerals) are not +efficient enough to use in practice so we present an example +demonstrating binary natural numbers instead: ```rust struct _0; // 0 bit @@ -199,29 +197,41 @@ impl Nat for _1 {} impl Nat for (P, B) {} ``` -These can be used to index into tuples or HLists generically (linear -time generally or constant time up to a fixed number of -specializations). They can also be used to encode "sized" or "bounded" -data, like vectors: +These can be used to index into tuples or HLists generically, either +by specifying the path explicitly (e.g., `(a, b, c).at::<(_1, _0)>() +==> c`) or by providing a singleton term with the appropriate type +`(a, b, c).at((_1, _0)) ==> c`. Indexing is linear time in the general +case due to recursion, but can be made constant time for a fixed +number of specialized implementations. + +Type-level numbers can also be used to define "sized" or "bounded" +data, such as a vector indexed by its length: ```rust struct LengthVec(Vec); ``` -The type number can either be a phantom parameter `N` as above, or -represented concretely at the term-level (similar to list). In either -case, a length-safe API can be provided on top of types `Vec`. Because -the length is known statically, unsafe indexing would be allowable by -default. +Similar to the indexing example, the parameter `N` can either serve as +phantom data, or such a struct could also include a term-level +representation of N as another field. + +In either case, a length-safe API could be defined for container types +like `Vec`. "Unsafe" indexing (without bounds checking) into the +underlying container would be safe in general because the length of +the container would be known statically and reflected in the type of +the length-indexed wrapper. We could imagine an idealized API in the following fashion: ```rust // push, adding one to the length -fn push(x: A, xs: LengthVec) -> LengthVec; +fn push(xs: LengthVec, x: A) -> LengthVec; // pop, subtracting one from the length -fn pop(store: &mut A, xs: LengthVec) -> LengthVec; +fn pop(xs: LengthVec, store: &mut A) -> LengthVec; + +// look up an element at an index +fn at(xs: LengthVec, index: M) -> A; // append, adding the individual lengths fn append(xs: LengthVec, ys: LengthVec) -> LengthVec; @@ -230,23 +240,22 @@ fn append(xs: LengthVec, ys: LengthVec) -> Length fn iter(xs: LengthVec) -> LengthIterator; ``` -However, in order to be able to write something close to that in Rust, -we would need macros in types: +We can't write code like the above directly in Rust but we could +approximate it through type-level macros: ```rust // Expr! would expand + to Add::Output and integer constants to Nat!; see -// the HList append earlier in the RFC for a concrete example of how this -// might be defined +// the HList append earlier in the RFC for a concrete example Expr!(N + M) ==> >::Output // Nat! would expand integer literals to type-level binary naturals // and be implemented as a plugin for efficiency; see the following -// section for a concrete implementation +// section for a concrete example Nat!(4) ==> ((_1, _0), _0) -// Now we could expand the following type to something meaningful in Rust: +// `Expr!` and `Nat!` used for the LengthVec type: LengthVec ==> LengthVec>::Output> ==> LengthVec>::Output> @@ -255,9 +264,9 @@ LengthVec ##### Implementation of `Nat!` as a plugin The following code demonstrates concretely how `Nat!` can be -implemented as a plugin. As with the `HList!` example, this code is -already usable with the type macros implemented in the branch -referenced earlier in this RFC. +implemented as a plugin. As with the `HList!` example, this code (with +some additions) compiles and is usable with the type macros prototype +in the branch referenced earlier. For efficiency, the binary representation is first constructed as a string via iteration rather than recursively using `quote` macros. The @@ -268,9 +277,11 @@ string is then parsed as a type, returning an ast fragment. // nat_str(1024) // ==> (((((((((_1, _0), _0), _0), _0), _0), _0), _0), _0), _0) fn nat_str(mut num: u64) -> String { + let path = "_"; let mut res: String; if num < 2 { - res = num.to_string(); + res = String::from_str(path); + res.push_str(num.to_string().as_slice()); } else { let mut bin = vec![]; while num > 0 { @@ -278,9 +289,11 @@ fn nat_str(mut num: u64) -> String { num >>= 1; } res = ::std::iter::repeat('(').take(bin.len() - 1).collect(); + res.push_str(path); res.push_str(bin.pop().unwrap().to_string().as_slice()); for b in bin.iter().rev() { res.push_str(", "); + res.push_str(path); res.push_str(b.to_string().as_slice()); res.push_str(")"); } @@ -364,33 +377,32 @@ fn nats() { ##### Optimization of `Expr`! -Because `Expr!` could be implemented as a plugin, the opportunity -would exist to perform various optimizations of type-level expressions -during expansion. Partial evaluation would be one approach to -this. Furthermore, expansion-time optimizations would not necessarily -be limited to simple arithmetic expressions but could be used for -other data like HLists. +Defining `Expr!` as a plugin would provide an opportunity to perform +various optimizations of more complex type-level expressions during +expansion. Partial evaluation would be one way to achieve +this. Furthermore, expansion-time optimizations wouldn't be limited to +arithmetic expressions but could be used for other data like HLists. -##### Native alternatives: types parameterized by constant values +##### Builtin alternatives: types parameterized by constant values -This example with type-level naturals is meant to illustrate the kind -of patterns macros in types enable. I am not suggesting the standard -libraries adopt _this particular_ representation as a means to address -the more general issue of lack of numeric parameterization for -types. There is +The example with type-level naturals serves to illustrate some of the +patterns type macros enable. This RFC is not intended to address the +lack of constant value type parameterization and type-level numerics +specifically. There is [another RFC here](https://github.com/rust-lang/rfcs/pull/884) which -does propose extending the type system to allow parameterization over -constants. +proposes extending the type system to address those issue. #### Conversion from HList to Tuple -With type macros, it is possible to write macros that convert back and -forth between tuples and HLists. This is very powerful because it lets -us reuse all of the operations we define for HLists (appending, taking -length, adding/removing items, computing permutations, etc.) on tuples -just by converting to HList, computing, then convert back to a tuple. +With type macros, it is possible to define conversions back and forth +between tuples and HLists. This is very powerful because it lets us +reuse at the level of tuples all of the recursive operations we can +define for HLists (appending, taking length, adding/removing items, +computing permutations, etc.). -The conversion can be implemented in the following fashion: +Conversions can be defined using macros/plugins and function +traits. Type macros are useful in this example for the associated type +`Output` and method return type in the traits. ```rust // type-level macro for HLists @@ -443,7 +455,7 @@ fn invoke_for_seq_upto_expand<'cx>( None }.and_then(|iterations| { - // generate a token tree: A0, ..., An + // generate a token tree: A0, …, An let mut ctx = range(0, iterations * 2 - 1).flat_map(|k| { if k % 2 == 0 { token::str_to_ident(format!("A{}", (k / 2)).as_slice()) @@ -532,48 +544,44 @@ fn test_to_hlist() { # Drawbacks There seem to be few drawbacks to implementing this feature as an -extension of the existing macro machinery. The change adds a very -small amount of additional complexity to the +extension of the existing macro machinery. The change adds a small +amount of additional complexity to the [parser](https://github.com/freebroccolo/rust/blob/e09cb32bcc04029dc4c16790e2aaa9811af27f25/src/libsyntax/parse/parser.rs#L1547-L1560) and [conversion](https://github.com/freebroccolo/rust/blob/e4b826b7afa1b5496b41ddaa1666014046ac5704/src/librustc_typeck/astconv.rs#L1301-L1303) -but the changes are almost negligible. +but the changes are minimal. As with all feature proposals, it is possible that designs for future -extensions to the macro system or type system might somehow interfere -with this functionality but it seems unlikely unless they are -significant, breaking changes. +extensions to the macro system or type system might interfere with +this functionality but it seems unlikely unless they are significant, +breaking changes. # Alternatives -There are no direct alternatives to my knowledge. Extensions to the -type system like data kinds, singletons, and various more elaborate -forms of staged programming (so-called CTFE) could conceivably cover -some cases where macros in types might otherwise be used. It is -unlikely they would provide the same level of functionality as macros, -particularly where plugins are concerned. Instead, such features would -probably benefit from type macros too. - -Not implementing this feature would mean disallowing some useful -programming patterns. There are some discussions in the community -regarding more extensive changes to the type system to address some of -these patterns. However, type macros along with associated types can -already accomplish many of the same things without the significant -engineering cost in terms of changes to the type system. Either way, -type macros would not prevent additional extensions. +There are no _direct_ alternatives. Extensions to the type system like +data kinds, singletons, and other forms of staged programming +(so-called CTFE) might alleviate the need for type macros in some +cases, however it is unlikely that they would provide a comprehensive +replacement, particularly where plugins are concerned. + +Not implementing this feature would mean not taking some reasonably +low-effort steps toward making certain programming patterns +easier. One potential consequence of this might be more pressure to +significantly extend the type system and other aspects of the language +to compensate. # Unresolved questions ## Alternative syntax for macro invocations in types -There is a question as to whether macros in types should allow `<` and -`>` as delimiters for invocations, e.g. `Foo!`. However, this would -raise a number of additional complications and is not necessary to +There is a question as to whether type macros should allow `<` and `>` +as delimiters for invocations, e.g. `Foo!`. This would raise a +number of additional complications and is probably not necessary to consider for this RFC. If deemed desirable by the community, this -functionality can be proposed separately. +functionality should be proposed separately. ## Hygiene and type macros -This RFC does not address the topic of hygiene regarding macros in -types. It is not clear to me whether there are issues here or not but -it may be worth considering in further detail. +This RFC also does not address the topic of hygiene regarding macros +in types. It is not clear whether there are issues here or not but it +may be worth considering in further detail. From fcee8fc6002c45689e6292ed57bd22c69c06126d Mon Sep 17 00:00:00 2001 From: Darin Morrison Date: Thu, 26 Feb 2015 13:33:11 -0700 Subject: [PATCH 11/18] Reword/clarify motivation --- text/0000-type-macros.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/text/0000-type-macros.md b/text/0000-type-macros.md index b0249df0cce..cb5957e2cb1 100644 --- a/text/0000-type-macros.md +++ b/text/0000-type-macros.md @@ -11,17 +11,21 @@ Allow macros in type positions Macros are currently allowed in syntax fragments for expressions, items, and patterns, but not for types. This RFC proposes to lift that -restriction for the following reasons: +restriction. -1. Increase generality of the macro system - the limitation should be - removed in order to promote generality and to enable use cases which - would otherwise require resorting either more elaborate plugins or - macros at the item-level. +1. This would allow macros to be used more flexibly, avoiding the + need for more complex item-level macros or plugins in some + cases. For example, when creating trait implementations with + macros, it is sometimes useful to be able to define the + associated types using a nested type macro but this is + currently problematic. + +2. Enable more programming patterns, particularly with respect to + type level programming. Macros in type positions provide + convenient way to express recursion and choice. It is possible + to do the same thing purely through programming with associated + types but the resulting code can be cumbersome to read and write. -2. Enable more programming patterns - macros in type positions provide - a means to express **recursion** and **choice** within types in a - fashion that is still legible. Associated types alone can accomplish - the former (recursion/choice) but not the latter (legibility). # Detailed design From 8d8bf2ccbb6e25df775f848bbe99d3eef0a3aa8d Mon Sep 17 00:00:00 2001 From: Darin Morrison Date: Thu, 26 Feb 2015 13:44:01 -0700 Subject: [PATCH 12/18] Update links --- text/0000-type-macros.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-type-macros.md b/text/0000-type-macros.md index cb5957e2cb1..6ca8cbfc85f 100644 --- a/text/0000-type-macros.md +++ b/text/0000-type-macros.md @@ -550,9 +550,9 @@ fn test_to_hlist() { There seem to be few drawbacks to implementing this feature as an extension of the existing macro machinery. The change adds a small amount of additional complexity to the -[parser](https://github.com/freebroccolo/rust/blob/e09cb32bcc04029dc4c16790e2aaa9811af27f25/src/libsyntax/parse/parser.rs#L1547-L1560) +[parser](https://github.com/freebroccolo/rust/commit/a224739e92a3aa1febb67d6371988622bd141361) and -[conversion](https://github.com/freebroccolo/rust/blob/e4b826b7afa1b5496b41ddaa1666014046ac5704/src/librustc_typeck/astconv.rs#L1301-L1303) +[conversion](https://github.com/freebroccolo/rust/commit/9341232087991dee73713dc4521acdce11a799a2) but the changes are minimal. As with all feature proposals, it is possible that designs for future From 7b1994c3a899c2aa023bacf6fb62ded91420d0d5 Mon Sep 17 00:00:00 2001 From: Darin Morrison Date: Sat, 28 Feb 2015 13:52:51 -0700 Subject: [PATCH 13/18] Include additional details in code examples --- text/0000-type-macros.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/text/0000-type-macros.md b/text/0000-type-macros.md index 6ca8cbfc85f..9e342a444b2 100644 --- a/text/0000-type-macros.md +++ b/text/0000-type-macros.md @@ -57,11 +57,13 @@ length, adding/removing items, computing permutations, etc. Heterogeneous lists can be defined like so: ```rust +#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] struct Nil; // empty HList +#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] struct Cons(H, T); // cons cell of HList // trait to classify valid HLists -trait HList {} +trait HList: MarkerTrait {} impl HList for Nil {} impl HList for Cons {} ``` @@ -185,17 +187,17 @@ struct _0; // 0 bit struct _1; // 1 bit // classify valid bits -trait Bit {} +trait Bit: MarkerTrait {} impl Bit for _0 {} impl Bit for _1 {} // classify positive binary naturals -trait Pos {} +trait Pos: MarkerTrait {} impl Pos for _1 {} impl Pos for (P, B) {} // classify binary naturals with 0 -trait Nat {} +trait Nat: MarkerTrait {} impl Nat for _0 {} impl Nat for _1 {} impl Nat for (P, B) {} From ce82cc889e50eba54cd36bd6e023a059c7085d5a Mon Sep 17 00:00:00 2001 From: Darin Morrison Date: Sat, 28 Feb 2015 13:53:25 -0700 Subject: [PATCH 14/18] Modify `Expr!` to not need extra parentheses --- text/0000-type-macros.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/text/0000-type-macros.md b/text/0000-type-macros.md index 9e342a444b2..39ee64b449d 100644 --- a/text/0000-type-macros.md +++ b/text/0000-type-macros.md @@ -149,8 +149,10 @@ impl ops::Add for Cons w // type macro Expr allows us to expand the + operator appropriately macro_rules! Expr { - { $A:ty } => { $A }; - { $LHS:tt + $RHS:tt } => { >::Output }; + { ( $($LHS:tt)+ ) } => { Expr!($($LHS)+) }; + { HList ! [ $($LHS:tt)* ] + $($RHS:tt)+ } => { >::Output }; + { $LHS:tt + $($RHS:tt)+ } => { >::Output }; + { $LHS:ty } => { $LHS }; } // test demonstrating term level `xs + ys` and type level `Expr!(Xs + Ys)` @@ -164,10 +166,10 @@ fn test_append() { let xs: HList![&str, bool, Vec] = hlist!["foo", false, vec![]]; let ys: HList![u64, [u8; 3], ()] = hlist![0, [0, 1, 2], ()]; - // parentheses around compound types due to limitations in macro parsing; - // real implementation could use a plugin to avoid this - let zs: Expr!((HList![&str, bool, Vec]) + - (HList![u64, [u8; 3], ()])) + // demonstrate recursive expansion of Expr! + let zs: Expr!((HList![&str] + HList![bool] + HList![Vec]) + + (HList![u64] + HList![[u8; 3], ()]) + + HList![]) = aux(xs, ys); assert_eq!(zs, hlist!["foo", false, vec![], 0, [0, 1, 2], ()]) } From 89dc9fe8b8396ff464e66250257389bd3de415cb Mon Sep 17 00:00:00 2001 From: Darin Morrison Date: Sun, 1 Mar 2015 21:41:13 -0700 Subject: [PATCH 15/18] Renaming; fix examples to use MacEager --- text/0000-type-macros.md | 57 ++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/text/0000-type-macros.md b/text/0000-type-macros.md index 39ee64b449d..ecdf229acbc 100644 --- a/text/0000-type-macros.md +++ b/text/0000-type-macros.md @@ -281,10 +281,14 @@ string via iteration rather than recursively using `quote` macros. The string is then parsed as a type, returning an ast fragment. ```rust -// convert a u64 to a string representation of a type-level binary natural, e.g., -// nat_str(1024) -// ==> (((((((((_1, _0), _0), _0), _0), _0), _0), _0), _0), _0) -fn nat_str(mut num: u64) -> String { +// Convert a u64 to a string representation of a type-level binary natural, e.g., +// ast_as_str(1024) +// ==> "(((((((((_1, _0), _0), _0), _0), _0), _0), _0), _0), _0)" +fn ast_as_str<'cx>( + ecx: &'cx base::ExtCtxt, + mut num: u64, + mode: Mode, +) -> String { let path = "_"; let mut res: String; if num < 2 { @@ -306,17 +310,18 @@ fn nat_str(mut num: u64) -> String { res.push_str(")"); } } - return res; + res } -// Generate a parser with the nat string for `num` as input -fn nat_str_parser<'cx>( - ecx: &'cx mut base::ExtCtxt, +// Generate a parser which uses the nat's ast-as-string as its input +fn ast_parser<'cx>( + ecx: &'cx base::ExtCtxt, num: u64, + mode: Mode, ) -> parse::parser::Parser<'cx> { let filemap = ecx .codemap() - .new_filemap(String::from_str(""), nat_str(num)); + .new_filemap(String::from_str(""), ast_as_str(ecx, num, mode)); let reader = lexer::StringReader::new( &ecx.parse_sess().span_diagnostic, filemap); @@ -326,16 +331,16 @@ fn nat_str_parser<'cx>( Box::new(reader)) } -// Try to parse an integer literal and return a new parser for its nat -// string; this is used to create both a type-level `Nat!` with -// `nat_ty_expand` and term-level `nat!` macro with `nat_tm_expand` -pub fn nat_lit_parser<'cx>( - ecx: &'cx mut base::ExtCtxt, +// Try to parse an integer literal and return a new parser which uses +// the nat's ast-as-string as its input +pub fn lit_parser<'cx>( + ecx: &'cx base::ExtCtxt, args: &[ast::TokenTree], + mode: Mode, ) -> Option> { - let mut litp = ecx.new_parser_from_tts(args); - if let ast::Lit_::LitInt(lit, _) = litp.parse_lit().node { - Some(nat_str_parser(ecx, lit)) + let mut lit_parser = ecx.new_parser_from_tts(args); + if let ast::Lit_::LitInt(lit, _) = lit_parser.parse_lit().node { + Some(ast_parser(ecx, lit, mode)) } else { None } @@ -344,15 +349,15 @@ pub fn nat_lit_parser<'cx>( // Expand Nat!(n) to a type-level binary nat where n is an int literal, e.g., // Nat!(1024) // ==> (((((((((_1, _0), _0), _0), _0), _0), _0), _0), _0), _0) -pub fn nat_ty_expand<'cx>( +pub fn expand_ty<'cx>( ecx: &'cx mut base::ExtCtxt, span: codemap::Span, args: &[ast::TokenTree], ) -> Box { { - nat_lit_parser(ecx, args) - }.and_then(|mut natp| { - Some(base::MacTy::new(natp.parse_ty())) + lit_parser(ecx, args, Mode::Ty) + }.and_then(|mut ast_parser| { + Some(base::MacEager::ty(ast_parser.parse_ty())) }).unwrap_or_else(|| { ecx.span_err(span, "Nat!: expected an integer literal argument"); base::DummyResult::any(span) @@ -362,15 +367,15 @@ pub fn nat_ty_expand<'cx>( // Expand nat!(n) to a term-level binary nat where n is an int literal, e.g., // nat!(1024) // ==> (((((((((_1, _0), _0), _0), _0), _0), _0), _0), _0), _0) -pub fn nat_tm_expand<'cx>( +pub fn expand_tm<'cx>( ecx: &'cx mut base::ExtCtxt, span: codemap::Span, args: &[ast::TokenTree], ) -> Box { { - nat_lit_parser(ecx, args) - }.and_then(|mut natp| { - Some(base::MacExpr::new(natp.parse_expr())) + lit_parser(ecx, args, Mode::Tm) + }.and_then(|mut ast_parser| { + Some(base::MacEager::expr(ast_parser.parse_expr())) }).unwrap_or_else(|| { ecx.span_err(span, "nat!: expected an integer literal argument"); base::DummyResult::any(span) @@ -487,7 +492,7 @@ fn invoke_for_seq_upto_expand<'cx>( } // splice the impl fragments into the ast - Some(base::MacItems::new(items.into_iter())) + Some(base::MacEager::items(SmallVector::many(items))) }).unwrap_or_else(|| { ecx.span_err(span, "invoke_for_seq_upto!: expected an integer literal argument"); From 729ab73f1d43f3daec6f2d805673c285bb5df437 Mon Sep 17 00:00:00 2001 From: Darin Morrison Date: Sun, 1 Mar 2015 23:25:31 -0700 Subject: [PATCH 16/18] Modify macro example to match patterns --- text/0000-type-macros.md | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/text/0000-type-macros.md b/text/0000-type-macros.md index ecdf229acbc..3b588cb174e 100644 --- a/text/0000-type-macros.md +++ b/text/0000-type-macros.md @@ -80,8 +80,16 @@ At the term-level, this is an easy fix using macros: // term-level macro for HLists macro_rules! hlist { {} => { Nil }; - { $head:expr } => { Cons($head, Nil) }; + {=> $($elem:tt),+ } => { hlist_pat!($($elem),+) }; { $head:expr, $($tail:expr),* } => { Cons($head, hlist!($($tail),*)) }; + { $head:expr } => { Cons($head, Nil) }; +} + +// term-level HLists in patterns +macro_rules! hlist_pat { + {} => { Nil }; + { $head:pat, $($tail:tt),* } => { Cons($head, hlist_pat!($($tail),*)) }; + { $head:pat } => { Cons($head, Nil) }; } let xs = hlist!["foo", false, vec![0u64]]; @@ -102,8 +110,16 @@ well. The complete example follows: // term-level macro for HLists macro_rules! hlist { {} => { Nil }; - { $head:expr } => { Cons($head, Nil) }; + {=> $($elem:tt),+ } => { hlist_pat!($($elem),+) }; { $head:expr, $($tail:expr),* } => { Cons($head, hlist!($($tail),*)) }; + { $head:expr } => { Cons($head, Nil) }; +} + +// term-level HLists in patterns +macro_rules! hlist_pat { + {} => { Nil }; + { $head:pat, $($tail:tt),* } => { Cons($head, hlist_pat!($($tail),*)) }; + { $head:pat } => { Cons($head, Nil) }; } // type-level macro for HLists @@ -428,15 +444,16 @@ macro_rules! HList { // term-level macro for HLists macro_rules! hlist { {} => { Nil }; - { $head:expr } => { Cons($head, Nil) }; + {=> $($elem:tt),+ } => { hlist_pat!($($elem),+) }; { $head:expr, $($tail:expr),* } => { Cons($head, hlist!($($tail),*)) }; + { $head:expr } => { Cons($head, Nil) }; } // term-level HLists in patterns -macro_rules! hlist_match { +macro_rules! hlist_pat { {} => { Nil }; - { $head:ident } => { Cons($head, Nil) }; - { $head:ident, $($tail:ident),* } => { Cons($head, hlist_match!($($tail),*)) }; + { $head:pat, $($tail:tt),* } => { Cons($head, hlist_pat!($($tail),*)) }; + { $head:pat } => { Cons($head, Nil) }; } // `invoke_for_seq_upto` is a `higher-order` macro that takes the name @@ -512,7 +529,7 @@ macro_rules! impl_to_tuple { type Output = ($($seq,)*); extern "rust-call" fn call(&self, (this,): (HList![$($seq),*],)) -> ($($seq,)*) { match this { - hlist_match![$($seq),*] => ($($seq,)*) + hlist![=> $($seq),*] => ($($seq,)*) } } } From bf3201d0453aaecba16e2faeb8f3f75dfc184a05 Mon Sep 17 00:00:00 2001 From: Darin Morrison Date: Mon, 9 Mar 2015 21:07:39 -0600 Subject: [PATCH 17/18] Remove HList/tuple conversion example --- text/0000-type-macros.md | 150 --------------------------------------- 1 file changed, 150 deletions(-) diff --git a/text/0000-type-macros.md b/text/0000-type-macros.md index 3b588cb174e..c16751ddd35 100644 --- a/text/0000-type-macros.md +++ b/text/0000-type-macros.md @@ -421,156 +421,6 @@ specifically. There is [another RFC here](https://github.com/rust-lang/rfcs/pull/884) which proposes extending the type system to address those issue. -#### Conversion from HList to Tuple - -With type macros, it is possible to define conversions back and forth -between tuples and HLists. This is very powerful because it lets us -reuse at the level of tuples all of the recursive operations we can -define for HLists (appending, taking length, adding/removing items, -computing permutations, etc.). - -Conversions can be defined using macros/plugins and function -traits. Type macros are useful in this example for the associated type -`Output` and method return type in the traits. - -```rust -// type-level macro for HLists -macro_rules! HList { - {} => { Nil }; - { $head:ty } => { Cons<$head, Nil> }; - { $head:ty, $($tail:ty),* } => { Cons<$head, HList!($($tail),*)> }; -} - -// term-level macro for HLists -macro_rules! hlist { - {} => { Nil }; - {=> $($elem:tt),+ } => { hlist_pat!($($elem),+) }; - { $head:expr, $($tail:expr),* } => { Cons($head, hlist!($($tail),*)) }; - { $head:expr } => { Cons($head, Nil) }; -} - -// term-level HLists in patterns -macro_rules! hlist_pat { - {} => { Nil }; - { $head:pat, $($tail:tt),* } => { Cons($head, hlist_pat!($($tail),*)) }; - { $head:pat } => { Cons($head, Nil) }; -} - -// `invoke_for_seq_upto` is a `higher-order` macro that takes the name -// of another macro and a number and iteratively invokes the named -// macro with sequences of identifiers, e.g., -// -// invoke_for_seq_upto{ my_mac, 5 } -// ==> my_mac!{ A0, A1, A2, A3, A4 }; -// my_mac!{ A0, A1, A2, A3 }; -// my_mac!{ A0, A1, A2 }; -// ... -fn invoke_for_seq_upto_expand<'cx>( - ecx: &'cx mut base::ExtCtxt, - span: codemap::Span, - args: &[ast::TokenTree], -) -> Box { - let mut parser = ecx.new_parser_from_tts(args); - - // parse the macro name - let mac = parser.parse_ident(); - - // parse a comma - parser.expect(&token::Token::Comma); - - // parse the number of iterations - if let ast::Lit_::LitInt(lit, _) = parser.parse_lit().node { - Some(lit) - } else { - None - }.and_then(|iterations| { - - // generate a token tree: A0, …, An - let mut ctx = range(0, iterations * 2 - 1).flat_map(|k| { - if k % 2 == 0 { - token::str_to_ident(format!("A{}", (k / 2)).as_slice()) - .to_tokens(ecx) - .into_iter() - } else { - let span = codemap::DUMMY_SP; - let token = parse::token::Token::Comma; - vec![ast::TokenTree::TtToken(span, token)] - .into_iter() - } - }).collect::>(); - - // iterate over the ctx and generate impl syntax fragments - let mut items = vec![]; - let mut i = ctx.len(); - for _ in range(0, iterations) { - items.push(quote_item!(ecx, $mac!{ $ctx };).unwrap()); - i -= 2; - ctx.truncate(i); - } - - // splice the impl fragments into the ast - Some(base::MacEager::items(SmallVector::many(items))) - - }).unwrap_or_else(|| { - ecx.span_err(span, "invoke_for_seq_upto!: expected an integer literal argument"); - base::DummyResult::any(span) - }) -} - -pub struct ToHList; -pub struct ToTuple; - -// macro to implement conversion from hlist to tuple, -// e.g., ToTuple(hlist![…]) ==> (…,) -macro_rules! impl_to_tuple { - ($($seq:ident),*) => { - #[allow(non_snake_case)] - impl<$($seq,)*> Fn<(HList![$($seq),*],)> for ToTuple { - type Output = ($($seq,)*); - extern "rust-call" fn call(&self, (this,): (HList![$($seq),*],)) -> ($($seq,)*) { - match this { - hlist![=> $($seq),*] => ($($seq,)*) - } - } - } - } -} - -// macro to implement conversion from tuple to hlist, -// e.g., ToHList((…,)) ==> hlist![…] -macro_rules! impl_to_hlist { - ($($seq:ident),*) => { - #[allow(non_snake_case)] - impl<$($seq,)*> Fn<(($($seq,)*),)> for ToHList { - type Output = HList![$($seq),*]; - extern "rust-call" fn call(&self, (this,): (($($seq,)*),)) -> HList![$($seq),*] { - match this { - ($($seq,)*) => hlist![$($seq),*] - } - } - } - } -} - -// generate implementations up to length 32 -invoke_for_seq_upto!{ impl_to_tuple, 32 } -invoke_for_seq_upto!{ impl_to_hlist, 32 } - -// test converting an hlist to tuple -#[test] -fn test_to_tuple() { - assert_eq(ToTuple(hlist!["foo", true, (), vec![42u64]]), - ("foo", true, (), vec![42u64])) -} - -// test converting a tuple to hlist -#[test] -fn test_to_hlist() { - assert_eq(ToHList(("foo", true, (), vec![42u64])), - hlist!["foo", true, (), vec![42u64]]) -} -``` - # Drawbacks There seem to be few drawbacks to implementing this feature as an From 82c85e426b3125a5458b0a712979daa2ae111913 Mon Sep 17 00:00:00 2001 From: Darin Morrison Date: Tue, 10 Mar 2015 19:28:48 -0600 Subject: [PATCH 18/18] Remove additional examples --- text/0000-type-macros.md | 236 +-------------------------------------- 1 file changed, 2 insertions(+), 234 deletions(-) diff --git a/text/0000-type-macros.md b/text/0000-type-macros.md index c16751ddd35..6f906903695 100644 --- a/text/0000-type-macros.md +++ b/text/0000-type-macros.md @@ -43,9 +43,7 @@ case for the `Ty_` enum so that the parser can indicate a macro invocation in a type position. In other words, `TyMac` is added to the ast and handled analogously to `ExprMac`, `ItemMac`, and `PatMac`. -## Examples - -### Heterogeneous Lists +## Example: Heterogeneous Lists Heterogeneous lists are one example where the ability to express recursion via type macros is very useful. They can be used as an @@ -136,7 +134,7 @@ Operations on HLists can be defined by recursion, using traits with associated type outputs at the type-level and implementation methods at the term-level. -The HList append operation is provided as an example. type macros are +The HList append operation is provided as an example. Type macros are used to make writing append at the type level (see `Expr!`) more convenient than specifying the associated type projection manually: @@ -191,236 +189,6 @@ fn test_append() { } ``` -### Additional Examples ### - -#### Type-level numerics - -Type-level numerics are another area where type macros can be -useful. The more common unary encodings (Peano numerals) are not -efficient enough to use in practice so we present an example -demonstrating binary natural numbers instead: - -```rust -struct _0; // 0 bit -struct _1; // 1 bit - -// classify valid bits -trait Bit: MarkerTrait {} -impl Bit for _0 {} -impl Bit for _1 {} - -// classify positive binary naturals -trait Pos: MarkerTrait {} -impl Pos for _1 {} -impl Pos for (P, B) {} - -// classify binary naturals with 0 -trait Nat: MarkerTrait {} -impl Nat for _0 {} -impl Nat for _1 {} -impl Nat for (P, B) {} -``` - -These can be used to index into tuples or HLists generically, either -by specifying the path explicitly (e.g., `(a, b, c).at::<(_1, _0)>() -==> c`) or by providing a singleton term with the appropriate type -`(a, b, c).at((_1, _0)) ==> c`. Indexing is linear time in the general -case due to recursion, but can be made constant time for a fixed -number of specialized implementations. - -Type-level numbers can also be used to define "sized" or "bounded" -data, such as a vector indexed by its length: - -```rust -struct LengthVec(Vec); -``` - -Similar to the indexing example, the parameter `N` can either serve as -phantom data, or such a struct could also include a term-level -representation of N as another field. - -In either case, a length-safe API could be defined for container types -like `Vec`. "Unsafe" indexing (without bounds checking) into the -underlying container would be safe in general because the length of -the container would be known statically and reflected in the type of -the length-indexed wrapper. - -We could imagine an idealized API in the following fashion: - -```rust -// push, adding one to the length -fn push(xs: LengthVec, x: A) -> LengthVec; - -// pop, subtracting one from the length -fn pop(xs: LengthVec, store: &mut A) -> LengthVec; - -// look up an element at an index -fn at(xs: LengthVec, index: M) -> A; - -// append, adding the individual lengths -fn append(xs: LengthVec, ys: LengthVec) -> LengthVec; - -// produce a length respecting iterator from an indexed vector -fn iter(xs: LengthVec) -> LengthIterator; -``` - -We can't write code like the above directly in Rust but we could -approximate it through type-level macros: - -```rust -// Expr! would expand + to Add::Output and integer constants to Nat!; see -// the HList append earlier in the RFC for a concrete example -Expr!(N + M) - ==> >::Output - -// Nat! would expand integer literals to type-level binary naturals -// and be implemented as a plugin for efficiency; see the following -// section for a concrete example -Nat!(4) - ==> ((_1, _0), _0) - -// `Expr!` and `Nat!` used for the LengthVec type: -LengthVec - ==> LengthVec>::Output> - ==> LengthVec>::Output> -``` - -##### Implementation of `Nat!` as a plugin - -The following code demonstrates concretely how `Nat!` can be -implemented as a plugin. As with the `HList!` example, this code (with -some additions) compiles and is usable with the type macros prototype -in the branch referenced earlier. - -For efficiency, the binary representation is first constructed as a -string via iteration rather than recursively using `quote` macros. The -string is then parsed as a type, returning an ast fragment. - -```rust -// Convert a u64 to a string representation of a type-level binary natural, e.g., -// ast_as_str(1024) -// ==> "(((((((((_1, _0), _0), _0), _0), _0), _0), _0), _0), _0)" -fn ast_as_str<'cx>( - ecx: &'cx base::ExtCtxt, - mut num: u64, - mode: Mode, -) -> String { - let path = "_"; - let mut res: String; - if num < 2 { - res = String::from_str(path); - res.push_str(num.to_string().as_slice()); - } else { - let mut bin = vec![]; - while num > 0 { - bin.push(num % 2); - num >>= 1; - } - res = ::std::iter::repeat('(').take(bin.len() - 1).collect(); - res.push_str(path); - res.push_str(bin.pop().unwrap().to_string().as_slice()); - for b in bin.iter().rev() { - res.push_str(", "); - res.push_str(path); - res.push_str(b.to_string().as_slice()); - res.push_str(")"); - } - } - res -} - -// Generate a parser which uses the nat's ast-as-string as its input -fn ast_parser<'cx>( - ecx: &'cx base::ExtCtxt, - num: u64, - mode: Mode, -) -> parse::parser::Parser<'cx> { - let filemap = ecx - .codemap() - .new_filemap(String::from_str(""), ast_as_str(ecx, num, mode)); - let reader = lexer::StringReader::new( - &ecx.parse_sess().span_diagnostic, - filemap); - parser::Parser::new( - ecx.parse_sess(), - ecx.cfg(), - Box::new(reader)) -} - -// Try to parse an integer literal and return a new parser which uses -// the nat's ast-as-string as its input -pub fn lit_parser<'cx>( - ecx: &'cx base::ExtCtxt, - args: &[ast::TokenTree], - mode: Mode, -) -> Option> { - let mut lit_parser = ecx.new_parser_from_tts(args); - if let ast::Lit_::LitInt(lit, _) = lit_parser.parse_lit().node { - Some(ast_parser(ecx, lit, mode)) - } else { - None - } -} - -// Expand Nat!(n) to a type-level binary nat where n is an int literal, e.g., -// Nat!(1024) -// ==> (((((((((_1, _0), _0), _0), _0), _0), _0), _0), _0), _0) -pub fn expand_ty<'cx>( - ecx: &'cx mut base::ExtCtxt, - span: codemap::Span, - args: &[ast::TokenTree], -) -> Box { - { - lit_parser(ecx, args, Mode::Ty) - }.and_then(|mut ast_parser| { - Some(base::MacEager::ty(ast_parser.parse_ty())) - }).unwrap_or_else(|| { - ecx.span_err(span, "Nat!: expected an integer literal argument"); - base::DummyResult::any(span) - }) -} - -// Expand nat!(n) to a term-level binary nat where n is an int literal, e.g., -// nat!(1024) -// ==> (((((((((_1, _0), _0), _0), _0), _0), _0), _0), _0), _0) -pub fn expand_tm<'cx>( - ecx: &'cx mut base::ExtCtxt, - span: codemap::Span, - args: &[ast::TokenTree], -) -> Box { - { - lit_parser(ecx, args, Mode::Tm) - }.and_then(|mut ast_parser| { - Some(base::MacEager::expr(ast_parser.parse_expr())) - }).unwrap_or_else(|| { - ecx.span_err(span, "nat!: expected an integer literal argument"); - base::DummyResult::any(span) - }) -} - -#[test] -fn nats() { - let _: Nat!(42) = nat!(42); -} -``` - -##### Optimization of `Expr`! - -Defining `Expr!` as a plugin would provide an opportunity to perform -various optimizations of more complex type-level expressions during -expansion. Partial evaluation would be one way to achieve -this. Furthermore, expansion-time optimizations wouldn't be limited to -arithmetic expressions but could be used for other data like HLists. - -##### Builtin alternatives: types parameterized by constant values - -The example with type-level naturals serves to illustrate some of the -patterns type macros enable. This RFC is not intended to address the -lack of constant value type parameterization and type-level numerics -specifically. There is -[another RFC here](https://github.com/rust-lang/rfcs/pull/884) which -proposes extending the type system to address those issue. - # Drawbacks There seem to be few drawbacks to implementing this feature as an