【问题标题】:Recursive generator in Rust causing "recursive type" error; workaround?Rust 中的递归生成器导致“递归类型”错误;解决方法?
【发布时间】:2018-07-23 23:24:39
【问题描述】:

我有一个形式的构造:

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

Value 可以是 null、字符串或其他 Values 的向量,然后可以是这三个选项中的任何一个。

我想创建一个在 Value 中延迟迭代每个 String 的方法,尊重嵌套。我的第一次尝试看起来像这样:

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

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

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

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

        fn next(&mut self) -> Option<Self::Item> {
            match Pin::new(&mut 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[E0720]: opaque type expands to a recursive type
  --> src/main.rs:34:39
   |
34 |     pub fn iter_over<'a>(&'a self) -> impl Iterator<Item = &'a String> {
   |                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expands to a recursive type
   |
   = note: expanded type is `gen_to_iter::It<[generator@src/main.rs:35:23: 47:10 self:&'a Value for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8, 't9, 't10, 't11, 't12, 't13, 't14, 't15, 't16, 't17> {&'r Value, Value, &'s std::string::String, &'t0 std::string::String, (), &'t1 std::vec::Vec<Value>, fn(&'t2 std::vec::Vec<Value>) -> <&'t2 std::vec::Vec<Value> as std::iter::IntoIterator>::IntoIter {<&'t2 std::vec::Vec<Value> as std::iter::IntoIterator>::into_iter}, &'t3 std::vec::Vec<Value>, std::slice::Iter<'t4, Value>, std::slice::Iter<'t5, Value>, &'t6 Value, &'t7 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}, &'t9 Value, &'t10 Value, impl std::iter::Iterator, impl std::iter::Iterator, impl std::iter::Iterator, &'t14 std::string::String, &'t15 std::string::String, &'t16 std::string::String, &'t17 std::string::String, ()}]>`

除了放弃惰性方法并仅使用向量之外,我似乎无法找到解决方法。我可以在这里采取哪些潜在途径?

【问题讨论】:

    标签: rust generator


    【解决方案1】:

    当生成器屈服时,它们需要存储范围内的局部变量和超出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)
        }
    }
    

    更新:breaking change 导致上述解决方案不再有效。将迭代器装箱已经不够了。这是一个错误,原因与type T = Box&lt;T&gt;; 无效的原因几乎相同,即使struct T(Box&lt;T&gt;); 有效;只有命名类型可以递归。为了解决这个问题,我们必须将类型隐藏在 trait 对象后面。拳击仍然是必要的;生成器必须拥有内部迭代器,所以我们不能在这里使用引用。

    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 {
                            // An `impl trait` type cannot refer to itself, even with indirection.
                            // https://github.com/rust-lang/rust/pull/56074#issuecomment-442982242
                            let iter = Box::new(v.iter_over()) as Box<dyn Iterator<Item = &'a String>>;
                            for i in iter {
                                yield i;
                            }
                        }
                    },
                }
            };
    
            gen_to_iter(closure)
        }
    }
    

    【讨论】:

    • 谢谢!解释扩展的枚举非常清楚发生了什么。确实,我在尝试手动制作递归结构时遇到了同样的问题,并通过引入Box 来修复它们。
    • 刚刚从另一个问题来到这里,这不再编译,出现错误,可能是因为github.com/rust-lang/rust/pull/56074 "error[E0720]: opaque type expands to a recursive type"
    猜你喜欢
    • 2020-02-09
    • 2022-12-14
    • 1970-01-01
    • 1970-01-01
    • 2016-12-29
    • 2021-08-15
    • 2020-10-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多