Rust中的递归生成器导致循环错误;解决方法?

时间:2018-07-23 23:24:39

标签: rust generator

我有一个形式的构造:

pub enum Value {
    Nil,
    Str(String),
    Seq(Vec<Value>),
}

Value为null,字符串或其他Value的向量,它们可以依次为三个选项中的任何一个。

我想提出一种方法,该方法可以在嵌套String中对每个Value进行延迟迭代。我的第一次尝试看起来像这样:

#![feature(generators)]
#![feature(generator_trait)]

use std::ops::{Generator, GeneratorState};

fn gen_to_iter<G>(g: G) -> impl Iterator<Item = G::Yield>
where
    G: Generator<Return = ()>,
{
    struct It<G>(G);

    impl<G: Generator<Return = ()>> Iterator for It<G> {
        type Item = G::Yield;

        fn next(&mut self) -> Option<Self::Item> {
            unsafe {
                match self.0.resume() {
                    GeneratorState::Yielded(y) => Some(y),
                    GeneratorState::Complete(()) => None,
                }
            }
        }
    }

    It(g)
}

pub enum Value {
    Nil,
    Str(String),
    Seq(Vec<Value>),
}

impl Value {
    pub fn iter_over<'a>(&'a self) -> impl Iterator<Item = &'a String> {
        let closure = move || match *self {
            Value::Nil => {}
            Value::Str(ref s) => {
                yield s;
            }
            Value::Seq(ref vs) => {
                for v in vs {
                    for i in v.iter_over() {
                        yield i;
                    }
                }
            }
        };

        gen_to_iter(closure)
    }
}

fn main() {
    let val = Value::Seq(vec![Value::Str("test".to_string())]);

    for s in val.iter_over() {
        println!("{}", s);
    }
}

playground

运行上面的代码时,由于在另一个对iter_over的调用中调用iter_over,所以我得到一个关于周期发生的编译器错误:

error[E0391]: cycle detected when computing layout of `[generator@src/main.rs:36:23: 48:10 self:&Value for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8, 't9, 't10, 't11> {&'r Value, Value, &'s std::string::String, (), &'t0 std::vec::Vec<Value>, fn(&'t1 std::vec::Vec<Value>) -> <&'t1 std::vec::Vec<Value> as std::iter::IntoIterator>::IntoIter {<&'t1 std::vec::Vec<Value> as std::iter::IntoIterator>::into_iter}, std::slice::Iter<'t2, Value>, std::slice::Iter<'t3, Value>, &'t4 Value, &'t5 Value, fn(impl std::iter::Iterator) -> <impl std::iter::Iterator as std::iter::IntoIterator>::IntoIter {<impl std::iter::Iterator as std::iter::IntoIterator>::into_iter}, &'t7 Value, impl std::iter::Iterator, &'t9 std::string::String, &'t10 std::string::String, &'t11 std::string::String}]`
  |
note: ...which requires computing layout of `gen_to_iter::It<[generator@src/main.rs:36:23: 48:10 self:&Value for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8, 't9, 't10, 't11> {&'r Value, Value, &'s std::string::String, (), &'t0 std::vec::Vec<Value>, fn(&'t1 std::vec::Vec<Value>) -> <&'t1 std::vec::Vec<Value> as std::iter::IntoIterator>::IntoIter {<&'t1 std::vec::Vec<Value> as std::iter::IntoIterator>::into_iter}, std::slice::Iter<'t2, Value>, std::slice::Iter<'t3, Value>, &'t4 Value, &'t5 Value, fn(impl std::iter::Iterator) -> <impl std::iter::Iterator as std::iter::IntoIterator>::IntoIter {<impl std::iter::Iterator as std::iter::IntoIterator>::into_iter}, &'t7 Value, impl std::iter::Iterator, &'t9 std::string::String, &'t10 std::string::String, &'t11 std::string::String}]>`...
  = note: ...which again requires computing layout of `[generator@src/main.rs:36:23: 48:10 self:&Value for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8, 't9, 't10, 't11> {&'r Value, Value, &'s std::string::String, (), &'t0 std::vec::Vec<Value>, fn(&'t1 std::vec::Vec<Value>) -> <&'t1 std::vec::Vec<Value> as std::iter::IntoIterator>::IntoIter {<&'t1 std::vec::Vec<Value> as std::iter::IntoIterator>::into_iter}, std::slice::Iter<'t2, Value>, std::slice::Iter<'t3, Value>, &'t4 Value, &'t5 Value, fn(impl std::iter::Iterator) -> <impl std::iter::Iterator as std::iter::IntoIterator>::IntoIter {<impl std::iter::Iterator as std::iter::IntoIterator>::into_iter}, &'t7 Value, impl std::iter::Iterator, &'t9 std::string::String, &'t10 std::string::String, &'t11 std::string::String}]`, completing the cycle
note: cycle used when compile_codegen_unit

除了放弃一种懒惰的方法,而只是使用向量,我似乎无法找出解决方法。我可以在这里采取哪些潜在途径?

1 个答案:

答案 0 :(得分:4)

生成器屈服时,它们需要存储范围内的局部变量和其他超出yield表达式的值。生成器是具有初始状态的一个变体,每个yield表达式的一个变体和“完成”状态的一个无状态变体的枚举。 iter_over中定义的生成器具有一个变体(用于yield i),该变体必须存储另一个具有相同生成器类型的实例(间接地,因为它包装在It中)。简化后,您最终得到的是这样的类型:

enum State<'a> {
    Seq(std::slice::Iter<'a, Value>, State<'a>),
    Done,
}

此类型无效,编译器会告诉我们原因以及解决方法:

error[E0072]: recursive type `State` has infinite size
  --> src/main.rs:60:1
   |
60 | enum State<'a> {
   | ^^^^^^^^^^^^^^ recursive type has infinite size
61 |     Seq(std::slice::Iter<'a, Value>, State<'a>),
   |                                      --------- recursive without indirection
   |
   = help: insert indirection (e.g., a `Box`, `Rc`, or `&`) at some point to make `State` representable

我们可以将编译器给出的建议应用于您的情况:我们可以将内部迭代器包装在Box中,以避免无限大小的问题。

impl Value {
    pub fn iter_over<'a>(&'a self) -> impl Iterator<Item = &'a String> {
        let closure = move || {
            match *self {
                Value::Nil => {},
                Value::Str(ref s) => { yield s; },
                Value::Seq(ref vs) => {
                    for v in vs {
                        // This Box is necessary to give the generator a finite size.
                        for i in Box::new(v.iter_over()) {
                            yield i;
                        }
                    }
                },
            }
        };

        gen_to_iter(closure)
    }
}