【问题标题】:Why are recursive struct types illegal in Rust?为什么递归结构类型在 Rust 中是非法的?
【发布时间】:2020-04-21 06:00:59
【问题描述】:

我正在尝试一些随机的东西来加深我对 Rust 的理解。我just ran into the following error with this code

struct Person {
    mother: Option<Person>,
    father: Option<Person>,
    partner: Option<Person>,
}

pub fn main() {
    let susan = Person {
        mother: None,
        father: None,
        partner: None,
    };

    let john = Person {
        mother: None,
        father: None,
        partner: Some(susan),
    };
}

The error is:

error[E0072]: recursive type `Person` has infinite size
 --> src/main.rs:1:1
  |
1 | struct Person {
  | ^^^^^^^^^^^^^ recursive type has infinite size
2 |     mother: Option<Person>,
  |     ---------------------- recursive without indirection
3 |     father: Option<Person>,
  |     ---------------------- recursive without indirection
4 |     partner: Option<Person>,
  |     ----------------------- recursive without indirection
  |
  = help: insert indirection (e.g., a `Box`, `Rc`, or `&`) at some point to make `Person` representable

我知道如果I put the Person in a Boxso this works,我可以修复它:

struct Person {
    mother: Option<Box<Person>>,
    father: Option<Box<Person>>,
    partner: Option<Box<Person>>,
}

pub fn main() {
    let susan = Person {
        mother: None,
        father: None,
        partner: None,
    };

    let john = Person {
        mother: None,
        father: None,
        partner: Some(Box::new(susan)),
    };
}

我想了解这背后的完整故事。我知道装箱意味着它将存储在堆上而不是堆栈上,但我不明白为什么这种间接是必要的。

【问题讨论】:

    标签: rust


    【解决方案1】:

    structs 和enums(和元组)内的数据直接内联存储在结构值的内存中。给定一个类似

    的结构
    struct Recursive {
        x: u8,
        y: Option<Recursive>
    }
    

    让我们计算大小:size_of::&lt;Recursive&gt;()。显然它有来自x 字段的1 个字节,然后Option 的大小为1(用于判别式)+size_of::&lt;Recursive&gt;()(用于包含的数据),因此,总而言之,大小是总和:

    size_of::<Recursive>() == 2 + size_of::<Recursive>()
    

    也就是说,大小必须是无限的。

    另一种看待它的方式是重复扩展Recursive(为清楚起见,作为元组):

    Recursive ==
    (u8, Option<Recursive>) ==
    (u8, Option<(u8, Option<Recursive>)>) ==
    (u8, Option<(u8, Option<(u8, Option<Recursive>)>)>) ==
    ...
    

    所有这些都内联存储在一块内存中。

    Box&lt;T&gt; 是一个指针,即它具有固定大小,因此 (u8, Option&lt;Box&lt;Recursive&gt;&gt;) 是 1 + 8 个字节。 (看待Box&lt;T&gt; 的一种方式是它是一个普通的T,并保证它具有固定大小。)

    【讨论】:

    • 注意:结构的实例只有在无限家族树的情况下才具有无限大小。但是类型的大小仅基于类型信息,因此如果类型具有存储无限信息的潜力,则需要分配无限字节(在堆栈上)。间接通过仅将根节点放入堆栈来打破这个循环。
    • 我很困惑:如果这种类型只包含一个 Option 包含一个引用(或 None)和一个 u8,为什么它必须在堆上?似乎内存大小可以从参考大小加上 u8 的一个字节得出。
    • @Jackalope Recursive 类型中没有指针或引用。你的意思是像struct Recursive&lt;'a&gt; { x: u8, y: Option&lt;&amp;'a Recursive&lt;'a&gt;&gt; } 这样的变体吗?如果是这样,是的,该类型的大小是有限的(与Box 版本的大小相同)并且不需要堆分配。使用堆分配的变体(BoxRc/Arc)是最常见的形式,因为它最容易使用(可以只传递值而无需静态保证生命周期具有适当的关系) .
    【解决方案2】:

    The Rust Programming Language 对递归类型有这样的看法:

    Rust 需要在编译时知道一个类型占用了多少空间。一种在编译时无法知道其大小的类型是递归类型,其中一个值可以有另一个相同类型的值作为其自身的一部分。这种值的嵌套理论上可以无限继续,所以 Rust 不知道递归类型的值需要多少空间。但是,盒子的大小是已知的,因此通过在递归类型定义中插入盒子,我们就可以拥有递归类型。

    基本上,如果您不使用装箱,结构将是无限大的。例如,苏珊有母亲、父亲和伴侣,每个人都有母亲、父亲和伴侣……等等。装箱使用固定大小的指针和动态内存分配。

    【讨论】:

      猜你喜欢
      • 2012-11-29
      • 2015-08-28
      • 2018-07-01
      • 2016-07-04
      • 2021-05-03
      • 1970-01-01
      • 2018-01-27
      • 1970-01-01
      相关资源
      最近更新 更多