@@ -43,9 +43,7 @@ case for the `Ty_` enum so that the parser can indicate a macro
43
43
invocation in a type position. In other words, ` TyMac ` is added to the
44
44
ast and handled analogously to ` ExprMac ` , ` ItemMac ` , and ` PatMac ` .
45
45
46
- ## Examples
47
-
48
- ### Heterogeneous Lists
46
+ ## Example: Heterogeneous Lists
49
47
50
48
Heterogeneous lists are one example where the ability to express
51
49
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
136
134
associated type outputs at the type-level and implementation methods
137
135
at the term-level.
138
136
139
- The HList append operation is provided as an example. type macros are
137
+ The HList append operation is provided as an example. Type macros are
140
138
used to make writing append at the type level (see ` Expr! ` ) more
141
139
convenient than specifying the associated type projection manually:
142
140
@@ -191,236 +189,6 @@ fn test_append() {
191
189
}
192
190
```
193
191
194
- ### Additional Examples ###
195
-
196
- #### Type-level numerics
197
-
198
- Type-level numerics are another area where type macros can be
199
- useful. The more common unary encodings (Peano numerals) are not
200
- efficient enough to use in practice so we present an example
201
- demonstrating binary natural numbers instead:
202
-
203
- ``` rust
204
- struct _0 ; // 0 bit
205
- struct _1 ; // 1 bit
206
-
207
- // classify valid bits
208
- trait Bit : MarkerTrait {}
209
- impl Bit for _0 {}
210
- impl Bit for _1 {}
211
-
212
- // classify positive binary naturals
213
- trait Pos : MarkerTrait {}
214
- impl Pos for _1 {}
215
- impl <B : Bit , P : Pos > Pos for (P , B ) {}
216
-
217
- // classify binary naturals with 0
218
- trait Nat : MarkerTrait {}
219
- impl Nat for _0 {}
220
- impl Nat for _1 {}
221
- impl <B : Bit , P : Pos > Nat for (P , B ) {}
222
- ```
223
-
224
- These can be used to index into tuples or HLists generically, either
225
- by specifying the path explicitly (e.g., `(a, b, c).at::<(_ 1, _ 0)>()
226
- ==> c`) or by providing a singleton term with the appropriate type
227
- ` (a, b, c).at((_1, _0)) ==> c ` . Indexing is linear time in the general
228
- case due to recursion, but can be made constant time for a fixed
229
- number of specialized implementations.
230
-
231
- Type-level numbers can also be used to define "sized" or "bounded"
232
- data, such as a vector indexed by its length:
233
-
234
- ``` rust
235
- struct LengthVec <A , N : Nat >(Vec <A >);
236
- ```
237
-
238
- Similar to the indexing example, the parameter ` N ` can either serve as
239
- phantom data, or such a struct could also include a term-level
240
- representation of N as another field.
241
-
242
- In either case, a length-safe API could be defined for container types
243
- like ` Vec ` . "Unsafe" indexing (without bounds checking) into the
244
- underlying container would be safe in general because the length of
245
- the container would be known statically and reflected in the type of
246
- the length-indexed wrapper.
247
-
248
- We could imagine an idealized API in the following fashion:
249
-
250
- ``` rust
251
- // push, adding one to the length
252
- fn push <A , N : Nat >(xs : LengthVec <A , N >, x : A ) -> LengthVec <A , N + 1 >;
253
-
254
- // pop, subtracting one from the length
255
- fn pop <A , N : Nat >(xs : LengthVec <A , N + 1 >, store : & mut A ) -> LengthVec <A , N >;
256
-
257
- // look up an element at an index
258
- fn at <A , M : Nat , N : Nat , P : M < N >(xs : LengthVec <A , N >, index : M ) -> A ;
259
-
260
- // append, adding the individual lengths
261
- fn append <A , N : Nat , M : Nat >(xs : LengthVec <A , N >, ys : LengthVec <A , M >) -> LengthVec <A , N + M >;
262
-
263
- // produce a length respecting iterator from an indexed vector
264
- fn iter <A , N : Nat >(xs : LengthVec <A , N >) -> LengthIterator <A , N >;
265
- ```
266
-
267
- We can't write code like the above directly in Rust but we could
268
- approximate it through type-level macros:
269
-
270
- ``` rust
271
- // Expr! would expand + to Add::Output and integer constants to Nat!; see
272
- // the HList append earlier in the RFC for a concrete example
273
- Expr! (N + M )
274
- = => <N as Add <M >>:: Output
275
-
276
- // Nat! would expand integer literals to type-level binary naturals
277
- // and be implemented as a plugin for efficiency; see the following
278
- // section for a concrete example
279
- Nat! (4 )
280
- = => ((_1 , _0 ), _0 )
281
-
282
- // `Expr!` and `Nat!` used for the LengthVec type:
283
- LengthVec <A , Expr! (N + 3 )>
284
- = => LengthVec <A , <N as Add < Nat! (3 )>>:: Output >
285
- = => LengthVec <A , <N as Add <(_1 , _1 )>>:: Output >
286
- ```
287
-
288
- ##### Implementation of ` Nat! ` as a plugin
289
-
290
- The following code demonstrates concretely how ` Nat! ` can be
291
- implemented as a plugin. As with the ` HList! ` example, this code (with
292
- some additions) compiles and is usable with the type macros prototype
293
- in the branch referenced earlier.
294
-
295
- For efficiency, the binary representation is first constructed as a
296
- string via iteration rather than recursively using ` quote ` macros. The
297
- string is then parsed as a type, returning an ast fragment.
298
-
299
- ``` rust
300
- // Convert a u64 to a string representation of a type-level binary natural, e.g.,
301
- // ast_as_str(1024)
302
- // ==> "(((((((((_1, _0), _0), _0), _0), _0), _0), _0), _0), _0)"
303
- fn ast_as_str <'cx >(
304
- ecx : & 'cx base :: ExtCtxt ,
305
- mut num : u64 ,
306
- mode : Mode ,
307
- ) -> String {
308
- let path = " _" ;
309
- let mut res : String ;
310
- if num < 2 {
311
- res = String :: from_str (path );
312
- res . push_str (num . to_string (). as_slice ());
313
- } else {
314
- let mut bin = vec! [];
315
- while num > 0 {
316
- bin . push (num % 2 );
317
- num >>= 1 ;
318
- }
319
- res = :: std :: iter :: repeat ('(' ). take (bin . len () - 1 ). collect ();
320
- res . push_str (path );
321
- res . push_str (bin . pop (). unwrap (). to_string (). as_slice ());
322
- for b in bin . iter (). rev () {
323
- res . push_str (" , " );
324
- res . push_str (path );
325
- res . push_str (b . to_string (). as_slice ());
326
- res . push_str (" )" );
327
- }
328
- }
329
- res
330
- }
331
-
332
- // Generate a parser which uses the nat's ast-as-string as its input
333
- fn ast_parser <'cx >(
334
- ecx : & 'cx base :: ExtCtxt ,
335
- num : u64 ,
336
- mode : Mode ,
337
- ) -> parse :: parser :: Parser <'cx > {
338
- let filemap = ecx
339
- . codemap ()
340
- . new_filemap (String :: from_str (" <nat!>" ), ast_as_str (ecx , num , mode ));
341
- let reader = lexer :: StringReader :: new (
342
- & ecx . parse_sess (). span_diagnostic,
343
- filemap );
344
- parser :: Parser :: new (
345
- ecx . parse_sess (),
346
- ecx . cfg (),
347
- Box :: new (reader ))
348
- }
349
-
350
- // Try to parse an integer literal and return a new parser which uses
351
- // the nat's ast-as-string as its input
352
- pub fn lit_parser <'cx >(
353
- ecx : & 'cx base :: ExtCtxt ,
354
- args : & [ast :: TokenTree ],
355
- mode : Mode ,
356
- ) -> Option <parse :: parser :: Parser <'cx >> {
357
- let mut lit_parser = ecx . new_parser_from_tts (args );
358
- if let ast :: Lit_ :: LitInt (lit , _ ) = lit_parser . parse_lit (). node {
359
- Some (ast_parser (ecx , lit , mode ))
360
- } else {
361
- None
362
- }
363
- }
364
-
365
- // Expand Nat!(n) to a type-level binary nat where n is an int literal, e.g.,
366
- // Nat!(1024)
367
- // ==> (((((((((_1, _0), _0), _0), _0), _0), _0), _0), _0), _0)
368
- pub fn expand_ty <'cx >(
369
- ecx : & 'cx mut base :: ExtCtxt ,
370
- span : codemap :: Span ,
371
- args : & [ast :: TokenTree ],
372
- ) -> Box <base :: MacResult + 'cx > {
373
- {
374
- lit_parser (ecx , args , Mode :: Ty )
375
- }. and_then (| mut ast_parser | {
376
- Some (base :: MacEager :: ty (ast_parser . parse_ty ()))
377
- }). unwrap_or_else (|| {
378
- ecx . span_err (span , " Nat!: expected an integer literal argument" );
379
- base :: DummyResult :: any (span )
380
- })
381
- }
382
-
383
- // Expand nat!(n) to a term-level binary nat where n is an int literal, e.g.,
384
- // nat!(1024)
385
- // ==> (((((((((_1, _0), _0), _0), _0), _0), _0), _0), _0), _0)
386
- pub fn expand_tm <'cx >(
387
- ecx : & 'cx mut base :: ExtCtxt ,
388
- span : codemap :: Span ,
389
- args : & [ast :: TokenTree ],
390
- ) -> Box <base :: MacResult + 'cx > {
391
- {
392
- lit_parser (ecx , args , Mode :: Tm )
393
- }. and_then (| mut ast_parser | {
394
- Some (base :: MacEager :: expr (ast_parser . parse_expr ()))
395
- }). unwrap_or_else (|| {
396
- ecx . span_err (span , " nat!: expected an integer literal argument" );
397
- base :: DummyResult :: any (span )
398
- })
399
- }
400
-
401
- #[test]
402
- fn nats () {
403
- let _ : Nat! (42 ) = nat! (42 );
404
- }
405
- ```
406
-
407
- ##### Optimization of ` Expr ` !
408
-
409
- Defining ` Expr! ` as a plugin would provide an opportunity to perform
410
- various optimizations of more complex type-level expressions during
411
- expansion. Partial evaluation would be one way to achieve
412
- this. Furthermore, expansion-time optimizations wouldn't be limited to
413
- arithmetic expressions but could be used for other data like HLists.
414
-
415
- ##### Builtin alternatives: types parameterized by constant values
416
-
417
- The example with type-level naturals serves to illustrate some of the
418
- patterns type macros enable. This RFC is not intended to address the
419
- lack of constant value type parameterization and type-level numerics
420
- specifically. There is
421
- [ another RFC here] ( https://github.com/rust-lang/rfcs/pull/884 ) which
422
- proposes extending the type system to address those issue.
423
-
424
192
# Drawbacks
425
193
426
194
There seem to be few drawbacks to implementing this feature as an
0 commit comments