【问题标题】:Ownership tracking in Rust: Difference between Box<T> (heap) and T (stack)Rust 中的所有权跟踪:Box<T>(堆)和 T(栈)之间的区别
【发布时间】:2017-10-20 06:45:19
【问题描述】:

用编程语言 Rust 进行实验,我发现编译器能够非常准确地跟踪 堆栈上某个结构的字段 的移动(它确切地知道 什么 字段已移动)。 但是,当我将结构的一部分放入Box(即放入堆中)时,编译器不再能够为取消引用后发生的所有事情确定字段级移动的盒子。它将假设“盒子内”的整个结构已经移动。我们先来看一个一切都在栈上的例子:

struct OuterContainer {
    inner: InnerContainer
}

struct InnerContainer {
    val_a: ValContainer,
    val_b: ValContainer
}

struct ValContainer {
    i: i32
}


fn main() {
    // Note that the whole structure lives on the stack.
    let structure = OuterContainer {
        inner: InnerContainer {
            val_a: ValContainer { i: 42 },
            val_b: ValContainer { i: 100 }
        }
    };

    // Move just one field (val_a) of the inner container.
    let move_me = structure.inner.val_a;

    // We can still borrow the other field (val_b).
    let borrow_me = &structure.inner.val_b;
}

现在是相同的示例,但有一个小改动:我们将InnerContainer 放入一个盒子 (Box&lt;InnerContainer&gt;)。

struct OuterContainer {
    inner: Box<InnerContainer>
}

struct InnerContainer {
    val_a: ValContainer,
    val_b: ValContainer
}

struct ValContainer {
    i: i32
}


fn main() {
    // Note that the whole structure lives on the stack.
    let structure = OuterContainer {
        inner: Box::new(InnerContainer {
            val_a: ValContainer { i: 42 },
            val_b: ValContainer { i: 100 }
        })
    };

    // Move just one field (val_a) of the inner container.
    // Note that now, the inner container lives on the heap.
    let move_me = structure.inner.val_a;

    // We can no longer borrow the other field (val_b).
    let borrow_me = &structure.inner.val_b; // error: "value used after move"
}

我怀疑这与 堆栈的性质与堆的性质有关,前者是静态的(至少每个堆栈帧),而后者是动态的.也许由于某种原因我不能很好地表达/识别,编译器需要谨慎行事。

【问题讨论】:

  • i32 是 Copy 类型,因此数据被复制而不是移动。
  • 但是,我正在对周围的结构 (ValContainer) 进行操作,而不是对包含的整数进行操作。据我所知,自定义结构类型默认不是Copyable。
  • 是的,你是对的。没有正确阅读您的代码。

标签: dynamic rust heap-memory stack-memory memory-safety


【解决方案1】:

在抽象中,堆栈上的struct一种,只是一个通用名称下的一堆变量。编译器知道这一点,并且可以将结构分解为一组其他独立的堆栈变量。这让它可以独立跟踪每个字段的移动。

它不能用Box 或任何其他类型的自定义分配来做到这一点,因为编译器不控制Boxes。 Box 只是标准库中的一些代码,而不是语言的固有部分。 Box 无法推理自身的不同部分突然变得无效。当需要销毁Box 时,Drop 实现只知道销毁所有内容

换句话说:在堆栈上,编译器处于完全控制之下,因此可以做一些花哨的事情,比如分解结构并将它们分段移动。一旦自定义分配进入画面,所有的赌注都被取消了,编译器不得不退后并停止尝试聪明。

【讨论】:

  • @MightyNicM:我试图澄清“控制箱”部分。 Drop 之所以重要,是因为这会破坏 Box(或任何其他类型)的内容。如果编译器要从Box 中移出一部分,Box 就无法知道这一点。它的分配中间突然出现了一个洞,当它试图破坏这个洞时可能会导致问题。不是什么时候放的问题,是放什么的问题。编译器可以处理堆栈中的“漏洞”,但不能处理其他地方。
  • @MightyNicM:对。一旦编译器移动了一些东西,留在旧位置的位是无效的,不能被触摸。如果它们,您最终可能会出现双重释放和其他不良行为。
  • @MightyNicM:Rust 无法跟踪堆的生命周期。这在理论上可能是可能的,但我不相信它甚至是假设性的计划。
  • 重要的区别是析构函数,而不是堆栈与堆。如果您采用非盒装版本并为 OuterContainer 或 InnerContainer 实施 Drop,则不允许部分移动。
  • 一些小迂腐:编译器实际上确实知道 Box,并且在历史上已经使您能够执行对 Box 的结构可以执行的操作。我记得唯一仍然存在的是您可以摆脱对 Box 的取消引用,这是其他任何事情都无法做到的。大多数这些东西在 1.0 中被显着回滚,因为我们不想要编译器对一种类型进行一系列特殊分析(为什么不使用 Rc、Vec 等...?)。从那以后,人们一直在努力提出一般机制,以有原则的方式公开旧的 Box 分析。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-11-15
  • 2011-11-20
  • 1970-01-01
  • 1970-01-01
  • 2011-05-15
  • 1970-01-01
相关资源
最近更新 更多