索引与移位

在此节中我们将略去一些实际上与宏的联系不甚紧密的内容。本节我们的目标是,让用户可以通过索引a来访问数列中先前的值。a应该如同一个切口,让我们得以持续访问数列中最近几个(在本例中,两个)值。

通过采用封装类,我们可以相对简单地做到这点:

  1. struct IndexOffset<'a> {
  2. slice: &'a [u64; 2],
  3. offset: usize,
  4. }
  5. impl<'a> Index<usize> for IndexOffset<'a> {
  6. type Output = u64;
  7. #[inline(always)]
  8. fn index<'b>(&'b self, index: usize) -> &'b u64 {
  9. use std::num::Wrapping;
  10. let index = Wrapping(index);
  11. let offset = Wrapping(self.offset);
  12. let window = Wrapping(2);
  13. let real_index = index - offset + window;
  14. &self.slice[real_index.0]
  15. }
  16. }

附注:对于新接触Rust的人来说,生命周期的概念经常需要一番思考。我们给出一些简单的解释:'a'b是生命周期参数,它们被用于记录引用(即一个指向某些数据的借用指针)的有效期。在此例中,IndexOffset借用了一个指向我们迭代器数据的引用,因此,它需要记录该引用的有效期,记录者正是'a

我们用到'b,是因为Index::index函数(下标句法正是通过此函数实现的)的一个参数也需要生命周期。'a'b不一定在所有情况下都相同。我们并没有显式地声明'a'b之间有任何联系,但借用检查器(borrow checker)总会确保内存安全性不被意外破坏。

a地定义将随之变为:

  1. let a = IndexOffset { slice: &self.mem, offset: n };

如何处理TODO_shuffle_down_and_append是我们现在剩下的唯一问题了。我没能在标准库中寻得可以直接使用的方法,但自己造一个出来并不难。

  1. {
  2. use std::mem::swap;
  3. let mut swap_tmp = next_val;
  4. for i in (0..2).rev() {
  5. swap(&mut swap_tmp, &mut self.mem[i]);
  6. }
  7. }

它把新值替换至数组末尾,并把其他值向前移动一位。

附注:采用这种做法,将使得我们的代码可同时被用于不可拷贝(non-copyable)的类型。

至此,最终起作用的代码将是:

  1. macro_rules! recurrence {
  2. ( a[n]: $sty:ty = $($inits:expr),+ , ... , $recur:expr ) => { /* ... */ };
  3. }
  4. fn main() {
  5. /*
  6. let fib = recurrence![a[n]: u64 = 0, 1, ..., a[n-1] + a[n-2]];
  7. for e in fib.take(10) { println!("{}", e) }
  8. */
  9. let fib = {
  10. use std::ops::Index;
  11. struct Recurrence {
  12. mem: [u64; 2],
  13. pos: usize,
  14. }
  15. struct IndexOffset<'a> {
  16. slice: &'a [u64; 2],
  17. offset: usize,
  18. }
  19. impl<'a> Index<usize> for IndexOffset<'a> {
  20. type Output = u64;
  21. #[inline(always)]
  22. fn index<'b>(&'b self, index: usize) -> &'b u64 {
  23. use std::num::Wrapping;
  24. let index = Wrapping(index);
  25. let offset = Wrapping(self.offset);
  26. let window = Wrapping(2);
  27. let real_index = index - offset + window;
  28. &self.slice[real_index.0]
  29. }
  30. }
  31. impl Iterator for Recurrence {
  32. type Item = u64;
  33. #[inline]
  34. fn next(&mut self) -> Option<u64> {
  35. if self.pos < 2 {
  36. let next_val = self.mem[self.pos];
  37. self.pos += 1;
  38. Some(next_val)
  39. } else {
  40. let next_val = {
  41. let n = self.pos;
  42. let a = IndexOffset { slice: &self.mem, offset: n };
  43. (a[n-1] + a[n-2])
  44. };
  45. {
  46. use std::mem::swap;
  47. let mut swap_tmp = next_val;
  48. for i in (0..2).rev() {
  49. swap(&mut swap_tmp, &mut self.mem[i]);
  50. }
  51. }
  52. self.pos += 1;
  53. Some(next_val)
  54. }
  55. }
  56. }
  57. Recurrence { mem: [0, 1], pos: 0 }
  58. };
  59. for e in fib.take(10) { println!("{}", e) }
  60. }

注意我们改变了na的声明顺序,同时将它们(与递推表达式一同)用一个新区块包裹了起来。改变声明顺序的理由很明显(n得在a前被定义才能被a使用)。而包裹的理由则是:如果不,借用引用&self.mem将会阻止随后的swap操作(在某物仍存在其它别名时,无法对其进行改变)。包裹区块将确保&self.mem产生的借用在彼时过期。

顺带一提,将交换mem的代码包进区块里的唯一原因,正是为了缩减std::mem::swap的可用范畴,以保持代码整洁。

如果我们直接拿上段代码来跑,将会得到:

  1. 0
  2. 1
  3. 1
  4. 2
  5. 3
  6. 5
  7. 8
  8. 13
  9. 21
  10. 34

成功了!现在,让我们把这段代码复制粘贴进宏的展开部分,并把它们原本所在的位置换成一次宏调用。这样我们得到:

  1. macro_rules! recurrence {
  2. ( a[n]: $sty:ty = $($inits:expr),+ , ... , $recur:expr ) => {
  3. {
  4. /*
  5. What follows here is *literally* the code from before,
  6. cut and pasted into a new position. No other changes
  7. have been made.
  8. */
  9. use std::ops::Index;
  10. struct Recurrence {
  11. mem: [u64; 2],
  12. pos: usize,
  13. }
  14. struct IndexOffset<'a> {
  15. slice: &'a [u64; 2],
  16. offset: usize,
  17. }
  18. impl<'a> Index<usize> for IndexOffset<'a> {
  19. type Output = u64;
  20. #[inline(always)]
  21. fn index<'b>(&'b self, index: usize) -> &'b u64 {
  22. use std::num::Wrapping;
  23. let index = Wrapping(index);
  24. let offset = Wrapping(self.offset);
  25. let window = Wrapping(2);
  26. let real_index = index - offset + window;
  27. &self.slice[real_index.0]
  28. }
  29. }
  30. impl Iterator for Recurrence {
  31. type Item = u64;
  32. #[inline]
  33. fn next(&mut self) -> Option<u64> {
  34. if self.pos < 2 {
  35. let next_val = self.mem[self.pos];
  36. self.pos += 1;
  37. Some(next_val)
  38. } else {
  39. let next_val = {
  40. let n = self.pos;
  41. let a = IndexOffset { slice: &self.mem, offset: n };
  42. (a[n-1] + a[n-2])
  43. };
  44. {
  45. use std::mem::swap;
  46. let mut swap_tmp = next_val;
  47. for i in (0..2).rev() {
  48. swap(&mut swap_tmp, &mut self.mem[i]);
  49. }
  50. }
  51. self.pos += 1;
  52. Some(next_val)
  53. }
  54. }
  55. }
  56. Recurrence { mem: [0, 1], pos: 0 }
  57. }
  58. };
  59. }
  60. fn main() {
  61. let fib = recurrence![a[n]: u64 = 0, 1, ..., a[n-1] + a[n-2]];
  62. for e in fib.take(10) { println!("{}", e) }
  63. }

显然,宏的捕获尚未被用到,但这点很好改。不过,如果尝试编译上述代码,rustc会中止,并显示:

  1. recurrence.rs:69:45: 69:48 error: local ambiguity: multiple parsing options: built-in NTs expr ('inits') or 1 other options.
  2. recurrence.rs:69 let fib = recurrence![a[n]: u64 = 0, 1, ..., a[n-1] + a[n-2]];
  3. ^~~

这里我们撞上了macro_rules的一处限制。问题出在那第二个逗号上。当在展开过程中遇见它时,编译器无法决定是该将它解析成inits中的又一个表达式,还是解析成...。很遗憾,它不够聪明,没办法意识到...不是一个有效的表达式,所以它选择了放弃。理论上来说,上述代码应该能奏效,但当前它并不能。

附注:有关宏系统如何解读我们的规则,我之前的确撒了点小谎。通常来说,宏系统确实应当如我前述的那般运作,但在这里它没有。macro_rules的机制,由此看来,是存在一些小毛病的;我们得记得偶尔去做一些调控,好让它我们期许的那般运作。

在本例中,问题有两个。其一,宏系统不清楚各式各样的语法元素(如表达式)可由什么样的东西构成,或不能由什么样的东西构成;那是语法解析器的工作。其二,在试图捕获复合语法元素(如表达式)的过程中,它无法不100%地首先陷入该捕获中去。

换句话说,宏系统可以向语法解析器发出请求,让后者试图把某段输入当作表达式来进行解析;但此间无论语法解析器遇见任何问题,都将中止整个进程以示回应。目前,宏系统处理这种窘境的唯一方式,就是对任何可能产生此类问题的情境加以禁止。

好的一面在于,对于这摊子情况,没有任何人感到高兴。关键词macro早已被预留,以备未来更加严密的宏系统使用。直到那天来临之前,我们还是只得该怎么做就怎么做。

还好,修正方案也很简单:从宏句法中去掉逗号即可。出于平衡考量,我们将移除...双边的逗号:[^译注1]

  1. macro_rules! recurrence {
  2. ( a[n]: $sty:ty = $($inits:expr),+ ... $recur:expr ) => {
  3. // ^~~ changed
  4. /* ... */
  5. # // Cheat :D
  6. # (vec![0u64, 1, 2, 3, 5, 8, 13, 21, 34]).into_iter()
  7. };
  8. }
  9. fn main() {
  10. let fib = recurrence![a[n]: u64 = 0, 1 ... a[n-1] + a[n-2]];
  11. // ^~~ changed
  12. for e in fib.take(10) { println!("{}", e) }
  13. }

成功!现在,我们该将捕获部分捕获到的内容替代进展开部分中了。

[^译注1]: 在目前的稳定版本(Rust 1.14.0)下,去掉双边逗号后的代码无法通过编译。rustc报错“error: $inits:expr may be followed by ..., which is not allowed for expr fragments”。解决方案是将这两处逗号替换为其它字面值,如分号;与之前的捕获所用分隔符不同即可。

替换

在宏中替换你捕获到的内容相对简单,通过$sty:ty捕获到的内容可用$sty来替换。好,让我们换掉那些u64吧:

  1. macro_rules! recurrence {
  2. ( a[n]: $sty:ty = $($inits:expr),+ ... $recur:expr ) => {
  3. {
  4. use std::ops::Index;
  5. struct Recurrence {
  6. mem: [$sty; 2],
  7. // ^~~~ changed
  8. pos: usize,
  9. }
  10. struct IndexOffset<'a> {
  11. slice: &'a [$sty; 2],
  12. // ^~~~ changed
  13. offset: usize,
  14. }
  15. impl<'a> Index<usize> for IndexOffset<'a> {
  16. type Output = $sty;
  17. // ^~~~ changed
  18. #[inline(always)]
  19. fn index<'b>(&'b self, index: usize) -> &'b $sty {
  20. // ^~~~ changed
  21. use std::num::Wrapping;
  22. let index = Wrapping(index);
  23. let offset = Wrapping(self.offset);
  24. let window = Wrapping(2);
  25. let real_index = index - offset + window;
  26. &self.slice[real_index.0]
  27. }
  28. }
  29. impl Iterator for Recurrence {
  30. type Item = $sty;
  31. // ^~~~ changed
  32. #[inline]
  33. fn next(&mut self) -> Option<$sty> {
  34. // ^~~~ changed
  35. /* ... */
  36. # if self.pos < 2 {
  37. # let next_val = self.mem[self.pos];
  38. # self.pos += 1;
  39. # Some(next_val)
  40. # } else {
  41. # let next_val = {
  42. # let n = self.pos;
  43. # let a = IndexOffset { slice: &self.mem, offset: n };
  44. # (a[n-1] + a[n-2])
  45. # };
  46. #
  47. # {
  48. # use std::mem::swap;
  49. #
  50. # let mut swap_tmp = next_val;
  51. # for i in (0..2).rev() {
  52. # swap(&mut swap_tmp, &mut self.mem[i]);
  53. # }
  54. # }
  55. #
  56. # self.pos += 1;
  57. # Some(next_val)
  58. # }
  59. }
  60. }
  61. Recurrence { mem: [1, 1], pos: 0 }
  62. }
  63. };
  64. }
  65. fn main() {
  66. let fib = recurrence![a[n]: u64 = 0, 1 ... a[n-1] + a[n-2]];
  67. for e in fib.take(10) { println!("{}", e) }
  68. }

现在让我们来尝试更难的:如何将inits同时转变为字面值[0, 1]以及数组类型[$sty; 2]。首先我们试试:

  1. Recurrence { mem: [$($inits),+], pos: 0 }
  2. // ^~~~~~~~~~~ changed

此段代码与捕获的效果正好相反:将inits捕得的内容排列开来,总共有1或多次,每条内容之间用逗号分隔。展开的结果与期望一致,我们得到标记序列:0, 1

不过,通过inits转换出字面值2需要一些技巧。没有直接可行的方法,但我们可以通过另一个宏做到。我们一步一步来。

  1. macro_rules! count_exprs {
  2. /* ??? */
  3. # () => {}
  4. }
  5. # fn main() {}

先写显而易见的情况:未给表达式时,我们期望count_exprs展开为字面值0

  1. macro_rules! count_exprs {
  2. () => (0);
  3. // ^~~~~~~~~~ added
  4. }
  5. # fn main() {
  6. # const _0: usize = count_exprs!();
  7. # assert_eq!(_0, 0);
  8. # }

附注:你可能已经注意到了,这里的展开部分我用的是括号而非花括号。macro_rules其实不关心你用的是什么,只要它成对匹配即可:( ){ }[ ]。实际上,宏本身的匹配符(即紧跟宏名称后的匹配符)、语法规则外的匹配符及相应展开部分外的匹配符都可以替换。

调用宏时的括号也可被替换,但有些限制:当宏被以{...}(...);形式调用时,它总是会被解析为一个条目(item,比如,structfn声明)。在函数体内部时,这一特征很重要,它将消除“解析成表达式”和“解析成语句”之间的歧义。

有一个表达式的情况该怎么办?应该展开为字面值1

  1. macro_rules! count_exprs {
  2. () => (0);
  3. ($e:expr) => (1);
  4. // ^~~~~~~~~~~~~~~~~ added
  5. }
  6. # fn main() {
  7. # const _0: usize = count_exprs!();
  8. # const _1: usize = count_exprs!(x);
  9. # assert_eq!(_0, 0);
  10. # assert_eq!(_1, 1);
  11. # }

两个呢?

  1. macro_rules! count_exprs {
  2. () => (0);
  3. ($e:expr) => (1);
  4. ($e0:expr, $e1:expr) => (2);
  5. // ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ added
  6. }
  7. # fn main() {
  8. # const _0: usize = count_exprs!();
  9. # const _1: usize = count_exprs!(x);
  10. # const _2: usize = count_exprs!(x, y);
  11. # assert_eq!(_0, 0);
  12. # assert_eq!(_1, 1);
  13. # assert_eq!(_2, 2);
  14. # }

通过递归调用重新表达,我们可将扩展部分“精简”出来:

  1. macro_rules! count_exprs {
  2. () => (0);
  3. ($e:expr) => (1);
  4. ($e0:expr, $e1:expr) => (1 + count_exprs!($e1));
  5. // ^~~~~~~~~~~~~~~~~~~~~ changed
  6. }
  7. # fn main() {
  8. # const _0: usize = count_exprs!();
  9. # const _1: usize = count_exprs!(x);
  10. # const _2: usize = count_exprs!(x, y);
  11. # assert_eq!(_0, 0);
  12. # assert_eq!(_1, 1);
  13. # assert_eq!(_2, 2);
  14. # }

这样做可行是因为,Rust可将1 + 1合并成一个常量。那么,三种表达式的情况呢?

  1. macro_rules! count_exprs {
  2. () => (0);
  3. ($e:expr) => (1);
  4. ($e0:expr, $e1:expr) => (1 + count_exprs!($e1));
  5. ($e0:expr, $e1:expr, $e2:expr) => (1 + count_exprs!($e1, $e2));
  6. // ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ added
  7. }
  8. # fn main() {
  9. # const _0: usize = count_exprs!();
  10. # const _1: usize = count_exprs!(x);
  11. # const _2: usize = count_exprs!(x, y);
  12. # const _3: usize = count_exprs!(x, y, z);
  13. # assert_eq!(_0, 0);
  14. # assert_eq!(_1, 1);
  15. # assert_eq!(_2, 2);
  16. # assert_eq!(_3, 3);
  17. # }

附注:你可能会想,我们是否能翻转这些规则的排列顺序。在此情境下,可以。但在有些情况下,宏系统可能会对此挑剔。如果你发现自己有一个包含多项规则的宏系统老是报错,或给出期望外的结果;但你发誓它应该能用,试着调换一下规则的排序吧。

我们希望你现在已经能看出规律。通过匹配至一个表达式加上0或多个表达式并展开成1+a,我们可以减少规则列表的数目:

  1. macro_rules! count_exprs {
  2. () => (0);
  3. ($head:expr) => (1);
  4. ($head:expr, $($tail:expr),*) => (1 + count_exprs!($($tail),*));
  5. // ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ changed
  6. }
  7. # fn main() {
  8. # const _0: usize = count_exprs!();
  9. # const _1: usize = count_exprs!(x);
  10. # const _2: usize = count_exprs!(x, y);
  11. # const _3: usize = count_exprs!(x, y, z);
  12. # assert_eq!(_0, 0);
  13. # assert_eq!(_1, 1);
  14. # assert_eq!(_2, 2);
  15. # assert_eq!(_3, 3);
  16. # }

仅对此例:这段代码并非计数仅有或其最好的方法。若有兴趣,稍后可以研读计数一节。

有此工具后,我们可再次修改recurrence,确定mem所需的大小。

  1. // added:
  2. macro_rules! count_exprs {
  3. () => (0);
  4. ($head:expr) => (1);
  5. ($head:expr, $($tail:expr),*) => (1 + count_exprs!($($tail),*));
  6. }
  7. macro_rules! recurrence {
  8. ( a[n]: $sty:ty = $($inits:expr),+ ... $recur:expr ) => {
  9. {
  10. use std::ops::Index;
  11. const MEM_SIZE: usize = count_exprs!($($inits),+);
  12. // ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ added
  13. struct Recurrence {
  14. mem: [$sty; MEM_SIZE],
  15. // ^~~~~~~~ changed
  16. pos: usize,
  17. }
  18. struct IndexOffset<'a> {
  19. slice: &'a [$sty; MEM_SIZE],
  20. // ^~~~~~~~ changed
  21. offset: usize,
  22. }
  23. impl<'a> Index<usize> for IndexOffset<'a> {
  24. type Output = $sty;
  25. #[inline(always)]
  26. fn index<'b>(&'b self, index: usize) -> &'b $sty {
  27. use std::num::Wrapping;
  28. let index = Wrapping(index);
  29. let offset = Wrapping(self.offset);
  30. let window = Wrapping(MEM_SIZE);
  31. // ^~~~~~~~ changed
  32. let real_index = index - offset + window;
  33. &self.slice[real_index.0]
  34. }
  35. }
  36. impl Iterator for Recurrence {
  37. type Item = $sty;
  38. #[inline]
  39. fn next(&mut self) -> Option<$sty> {
  40. if self.pos < MEM_SIZE {
  41. // ^~~~~~~~ changed
  42. let next_val = self.mem[self.pos];
  43. self.pos += 1;
  44. Some(next_val)
  45. } else {
  46. let next_val = {
  47. let n = self.pos;
  48. let a = IndexOffset { slice: &self.mem, offset: n };
  49. (a[n-1] + a[n-2])
  50. };
  51. {
  52. use std::mem::swap;
  53. let mut swap_tmp = next_val;
  54. for i in (0..MEM_SIZE).rev() {
  55. // ^~~~~~~~ changed
  56. swap(&mut swap_tmp, &mut self.mem[i]);
  57. }
  58. }
  59. self.pos += 1;
  60. Some(next_val)
  61. }
  62. }
  63. }
  64. Recurrence { mem: [$($inits),+], pos: 0 }
  65. }
  66. };
  67. }
  68. /* ... */
  69. #
  70. # fn main() {
  71. # let fib = recurrence![a[n]: u64 = 0, 1 ... a[n-1] + a[n-2]];
  72. #
  73. # for e in fib.take(10) { println!("{}", e) }
  74. # }

完成之后,我们开始替换最后的recur表达式。

  1. # macro_rules! count_exprs {
  2. # () => (0);
  3. # ($head:expr $(, $tail:expr)*) => (1 + count_exprs!($($tail),*));
  4. # }
  5. # macro_rules! recurrence {
  6. # ( a[n]: $sty:ty = $($inits:expr),+ ... $recur:expr ) => {
  7. # {
  8. # const MEMORY: uint = count_exprs!($($inits),+);
  9. # struct Recurrence {
  10. # mem: [$sty; MEMORY],
  11. # pos: uint,
  12. # }
  13. # struct IndexOffset<'a> {
  14. # slice: &'a [$sty; MEMORY],
  15. # offset: uint,
  16. # }
  17. # impl<'a> Index<uint, $sty> for IndexOffset<'a> {
  18. # #[inline(always)]
  19. # fn index<'b>(&'b self, index: &uint) -> &'b $sty {
  20. # let real_index = *index - self.offset + MEMORY;
  21. # &self.slice[real_index]
  22. # }
  23. # }
  24. # impl Iterator<u64> for Recurrence {
  25. /* ... */
  26. #[inline]
  27. fn next(&mut self) -> Option<u64> {
  28. if self.pos < MEMORY {
  29. let next_val = self.mem[self.pos];
  30. self.pos += 1;
  31. Some(next_val)
  32. } else {
  33. let next_val = {
  34. let n = self.pos;
  35. let a = IndexOffset { slice: &self.mem, offset: n };
  36. $recur
  37. // ^~~~~~ changed
  38. };
  39. {
  40. use std::mem::swap;
  41. let mut swap_tmp = next_val;
  42. for i in range(0, MEMORY).rev() {
  43. swap(&mut swap_tmp, &mut self.mem[i]);
  44. }
  45. }
  46. self.pos += 1;
  47. Some(next_val)
  48. }
  49. }
  50. /* ... */
  51. # }
  52. # Recurrence { mem: [$($inits),+], pos: 0 }
  53. # }
  54. # };
  55. # }
  56. # fn main() {
  57. # let fib = recurrence![a[n]: u64 = 1, 1 ... a[n-1] + a[n-2]];
  58. # for e in fib.take(10) { println!("{}", e) }
  59. # }

现在试图编译的话…

  1. recurrence.rs:77:48: 77:49 error: unresolved name `a`
  2. recurrence.rs:77 let fib = recurrence![a[n]: u64 = 0, 1 ... a[n-1] + a[n-2]];
  3. ^
  4. recurrence.rs:7:1: 74:2 note: in expansion of recurrence!
  5. recurrence.rs:77:15: 77:64 note: expansion site
  6. recurrence.rs:77:50: 77:51 error: unresolved name `n`
  7. recurrence.rs:77 let fib = recurrence![a[n]: u64 = 0, 1 ... a[n-1] + a[n-2]];
  8. ^
  9. recurrence.rs:7:1: 74:2 note: in expansion of recurrence!
  10. recurrence.rs:77:15: 77:64 note: expansion site
  11. recurrence.rs:77:57: 77:58 error: unresolved name `a`
  12. recurrence.rs:77 let fib = recurrence![a[n]: u64 = 0, 1 ... a[n-1] + a[n-2]];
  13. ^
  14. recurrence.rs:7:1: 74:2 note: in expansion of recurrence!
  15. recurrence.rs:77:15: 77:64 note: expansion site
  16. recurrence.rs:77:59: 77:60 error: unresolved name `n`
  17. recurrence.rs:77 let fib = recurrence![a[n]: u64 = 0, 1 ... a[n-1] + a[n-2]];
  18. ^
  19. recurrence.rs:7:1: 74:2 note: in expansion of recurrence!
  20. recurrence.rs:77:15: 77:64 note: expansion site

…等等,什么情况?这没道理…让我们看看宏究竟展开成了什么样子。

  1. $ rustc -Z unstable-options --pretty expanded recurrence.rs

参数--pretty expanded将促使rustc展开宏,并将输出的AST再重转为源代码。此选项当前被认定为是unstable,因此我们还要添加-Z unstable-options。输出的信息(经过整理格式后)如下;特别留意$recur被替换掉的位置:

  1. #![feature(no_std)]
  2. #![no_std]
  3. #[prelude_import]
  4. use std::prelude::v1::*;
  5. #[macro_use]
  6. extern crate std as std;
  7. fn main() {
  8. let fib = {
  9. use std::ops::Index;
  10. const MEM_SIZE: usize = 1 + 1;
  11. struct Recurrence {
  12. mem: [u64; MEM_SIZE],
  13. pos: usize,
  14. }
  15. struct IndexOffset<'a> {
  16. slice: &'a [u64; MEM_SIZE],
  17. offset: usize,
  18. }
  19. impl <'a> Index<usize> for IndexOffset<'a> {
  20. type Output = u64;
  21. #[inline(always)]
  22. fn index<'b>(&'b self, index: usize) -> &'b u64 {
  23. use std::num::Wrapping;
  24. let index = Wrapping(index);
  25. let offset = Wrapping(self.offset);
  26. let window = Wrapping(MEM_SIZE);
  27. let real_index = index - offset + window;
  28. &self.slice[real_index.0]
  29. }
  30. }
  31. impl Iterator for Recurrence {
  32. type Item = u64;
  33. #[inline]
  34. fn next(&mut self) -> Option<u64> {
  35. if self.pos < MEM_SIZE {
  36. let next_val = self.mem[self.pos];
  37. self.pos += 1;
  38. Some(next_val)
  39. } else {
  40. let next_val = {
  41. let n = self.pos;
  42. let a = IndexOffset{slice: &self.mem, offset: n,};
  43. a[n - 1] + a[n - 2]
  44. };
  45. {
  46. use std::mem::swap;
  47. let mut swap_tmp = next_val;
  48. {
  49. let result =
  50. match ::std::iter::IntoIterator::into_iter((0..MEM_SIZE).rev()) {
  51. mut iter => loop {
  52. match ::std::iter::Iterator::next(&mut iter) {
  53. ::std::option::Option::Some(i) => {
  54. swap(&mut swap_tmp, &mut self.mem[i]);
  55. }
  56. ::std::option::Option::None => break,
  57. }
  58. },
  59. };
  60. result
  61. }
  62. }
  63. self.pos += 1;
  64. Some(next_val)
  65. }
  66. }
  67. }
  68. Recurrence{mem: [0, 1], pos: 0,}
  69. };
  70. {
  71. let result =
  72. match ::std::iter::IntoIterator::into_iter(fib.take(10)) {
  73. mut iter => loop {
  74. match ::std::iter::Iterator::next(&mut iter) {
  75. ::std::option::Option::Some(e) => {
  76. ::std::io::_print(::std::fmt::Arguments::new_v1(
  77. {
  78. static __STATIC_FMTSTR: &'static [&'static str] = &["", "\n"];
  79. __STATIC_FMTSTR
  80. },
  81. &match (&e,) {
  82. (__arg0,) => [::std::fmt::ArgumentV1::new(__arg0, ::std::fmt::Display::fmt)],
  83. }
  84. ))
  85. }
  86. ::std::option::Option::None => break,
  87. }
  88. },
  89. };
  90. result
  91. }
  92. }

呃..这看起来完全合法!如果我们加上几条#![feature(...)]属性,并把它送去给一个nightly版本的rustc,甚至真能通过编译…究竟什么情况?!

附注:上述代码无法通过非nightly版rustc编译。这是因为,println!宏的展开结果依赖于编译器内部的细节,这些细节尚未被公开稳定化。

保持卫生

这儿的问题在于,Rust宏中的标识符具有卫生性。这就是说,出自不同上下文的标识符不可能发生冲突。作为演示,举个简单的例子。

  1. # /*
  2. macro_rules! using_a {
  3. ($e:expr) => {
  4. {
  5. let a = 42i;
  6. $e
  7. }
  8. }
  9. }
  10. let four = using_a!(a / 10);
  11. # */
  12. # fn main() {}

此宏接受一个表达式,然后把它包进一个定义了变量a的区块里。我们随后用它绕个弯子来求4。这个例子中实际上存在2种句法上下文,但我们看不见它们。为了帮助说明,我们给每个上下文都上一种不同的颜色。我们从未展开的代码开始上色,此时仅看得见一种上下文:

  1. macro_rules! using_a {
  2. ($e:expr) => {
  3. {
  4. let a = 42;
  5. $e
  6. }
  7. }
  8. }
  9. let four = using_a!(a / 10);

现在,展开宏调用。

  1. let four = {
  2. let a = 42;
  3. a / 10
  4. };

可以看到,在宏中定义的a与调用所提供的a处于不同的上下文中。因此,虽然它们的字母表示一致,编译器仍将它们视作完全不同的标识符。

宏的这一特性需要格外留意:它们可能会产出无法通过编译的AST;但同样的代码,手写或通过--pretty expanded转印出来则能够通过编译。

解决方案是,采用合适的句法上下文来捕获标识符。我们沿用上例,并作修改:

  1. macro_rules! using_a {
  2. ($a:ident, $e:expr) => {
  3. {
  4. let $a = 42;
  5. $e
  6. }
  7. }
  8. }
  9. let four = using_a!(a, a / 10);

现在它将展开为:

  1. let four = {
  2. let a = 42;
  3. a / 10
  4. };

上下文现在匹配了,编译通过。我们的recurrence!宏也可被如此调整:显式地捕获an即可。调整后我们得到:

  1. macro_rules! count_exprs {
  2. () => (0);
  3. ($head:expr) => (1);
  4. ($head:expr, $($tail:expr),*) => (1 + count_exprs!($($tail),*));
  5. }
  6. macro_rules! recurrence {
  7. ( $seq:ident [ $ind:ident ]: $sty:ty = $($inits:expr),+ ... $recur:expr ) => {
  8. // ^~~~~~~~~~ ^~~~~~~~~~ changed
  9. {
  10. use std::ops::Index;
  11. const MEM_SIZE: usize = count_exprs!($($inits),+);
  12. struct Recurrence {
  13. mem: [$sty; MEM_SIZE],
  14. pos: usize,
  15. }
  16. struct IndexOffset<'a> {
  17. slice: &'a [$sty; MEM_SIZE],
  18. offset: usize,
  19. }
  20. impl<'a> Index<usize> for IndexOffset<'a> {
  21. type Output = $sty;
  22. #[inline(always)]
  23. fn index<'b>(&'b self, index: usize) -> &'b $sty {
  24. use std::num::Wrapping;
  25. let index = Wrapping(index);
  26. let offset = Wrapping(self.offset);
  27. let window = Wrapping(MEM_SIZE);
  28. let real_index = index - offset + window;
  29. &self.slice[real_index.0]
  30. }
  31. }
  32. impl Iterator for Recurrence {
  33. type Item = $sty;
  34. #[inline]
  35. fn next(&mut self) -> Option<$sty> {
  36. if self.pos < MEM_SIZE {
  37. let next_val = self.mem[self.pos];
  38. self.pos += 1;
  39. Some(next_val)
  40. } else {
  41. let next_val = {
  42. let $ind = self.pos;
  43. // ^~~~ changed
  44. let $seq = IndexOffset { slice: &self.mem, offset: $ind };
  45. // ^~~~ changed
  46. $recur
  47. };
  48. {
  49. use std::mem::swap;
  50. let mut swap_tmp = next_val;
  51. for i in (0..MEM_SIZE).rev() {
  52. swap(&mut swap_tmp, &mut self.mem[i]);
  53. }
  54. }
  55. self.pos += 1;
  56. Some(next_val)
  57. }
  58. }
  59. }
  60. Recurrence { mem: [$($inits),+], pos: 0 }
  61. }
  62. };
  63. }
  64. fn main() {
  65. let fib = recurrence![a[n]: u64 = 0, 1 ... a[n-1] + a[n-2]];
  66. for e in fib.take(10) { println!("{}", e) }
  67. }

通过编译了!接下来,我们试试别的数列。

  1. # macro_rules! count_exprs {
  2. # () => (0);
  3. # ($head:expr) => (1);
  4. # ($head:expr, $($tail:expr),*) => (1 + count_exprs!($($tail),*));
  5. # }
  6. #
  7. # macro_rules! recurrence {
  8. # ( $seq:ident [ $ind:ident ]: $sty:ty = $($inits:expr),+ ... $recur:expr ) => {
  9. # {
  10. # use std::ops::Index;
  11. #
  12. # const MEM_SIZE: usize = count_exprs!($($inits),+);
  13. #
  14. # struct Recurrence {
  15. # mem: [$sty; MEM_SIZE],
  16. # pos: usize,
  17. # }
  18. #
  19. # struct IndexOffset<'a> {
  20. # slice: &'a [$sty; MEM_SIZE],
  21. # offset: usize,
  22. # }
  23. #
  24. # impl<'a> Index<usize> for IndexOffset<'a> {
  25. # type Output = $sty;
  26. #
  27. # #[inline(always)]
  28. # fn index<'b>(&'b self, index: usize) -> &'b $sty {
  29. # use std::num::Wrapping;
  30. #
  31. # let index = Wrapping(index);
  32. # let offset = Wrapping(self.offset);
  33. # let window = Wrapping(MEM_SIZE);
  34. #
  35. # let real_index = index - offset + window;
  36. # &self.slice[real_index.0]
  37. # }
  38. # }
  39. #
  40. # impl Iterator for Recurrence {
  41. # type Item = $sty;
  42. #
  43. # #[inline]
  44. # fn next(&mut self) -> Option<$sty> {
  45. # if self.pos < MEM_SIZE {
  46. # let next_val = self.mem[self.pos];
  47. # self.pos += 1;
  48. # Some(next_val)
  49. # } else {
  50. # let next_val = {
  51. # let $ind = self.pos;
  52. # let $seq = IndexOffset { slice: &self.mem, offset: $ind };
  53. # $recur
  54. # };
  55. #
  56. # {
  57. # use std::mem::swap;
  58. #
  59. # let mut swap_tmp = next_val;
  60. # for i in (0..MEM_SIZE).rev() {
  61. # swap(&mut swap_tmp, &mut self.mem[i]);
  62. # }
  63. # }
  64. #
  65. # self.pos += 1;
  66. # Some(next_val)
  67. # }
  68. # }
  69. # }
  70. #
  71. # Recurrence { mem: [$($inits),+], pos: 0 }
  72. # }
  73. # };
  74. # }
  75. #
  76. # fn main() {
  77. for e in recurrence!(f[i]: f64 = 1.0 ... f[i-1] * i as f64).take(10) {
  78. println!("{}", e)
  79. }
  80. # }

运行上述代码得到:

  1. 1
  2. 1
  3. 2
  4. 6
  5. 24
  6. 120
  7. 720
  8. 5040
  9. 40320
  10. 362880

成功了!