Rust中的所有权跟踪:Box <T>(堆)和T(堆栈)之间的差异

dom*_*min 4 heap stack dynamic rust memory-safety

尝试使用编程语言Rust,我发现编译器能够非常准确地跟踪堆栈中某些结构字段的移动(它确切地知道哪个字段已移动).但是,当我将结构的一部分放入a 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;
}
Run Code Online (Sandbox Code Playgroud)

现在是相同的例子,但只有一个小改动:我们把它InnerContainer放进一个盒子里(Box<InnerContainer>).

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"
}
Run Code Online (Sandbox Code Playgroud)

我怀疑它与堆栈的性质和堆的性质有关,前者是静态的(至少每个堆栈帧),而后者是动态的.也许编译器需要安全地使用它,因为我无法清楚地表达/识别.

DK.*_*DK. 12

在抽象的,一个struct在栈上是那种只是在一个共同的名字变量的一群.编译器知道这一点,并且可以将结构分解为一组独立的堆栈变量.这使它可以独立跟踪每个场的移动.

它不能用a Box或任何其他类型的自定义分配来执行,因为编译器不控制Boxes. Box只是标准库中的一些代码,而不是语言的固有部分. Box无法推断自身的不同部分突然变得无效.当摧毁一个时Box,它的Drop实现只知道毁灭一切.

换句话说:在堆栈上,编译器处于完全控制状态,因此可以执行奇特的操作,例如破坏结构并逐个移动它们.一旦自定义分配进入图片,所有投注都会关闭,编译器必须退出并停止尝试变得聪明.

  • @MightyNicM:我试图澄清"控制箱"部分."掉落"的原因很重要,因为这就是破坏"盒子"(或任何其他类型)内容的原因.如果编译器将一个部分移出`Box`,那么`Box`无法知道这一点.在它的分配中间突然出现一个洞,当它试图摧毁所述洞时可能会引起问题.这不是什么时候放弃,这是*什么*下降的问题.编译器可以处理堆栈中的"漏洞",但不管其他地方. (5认同)
  • @MightyNicM:对.一旦编译器移动了某些东西,旧位置留下的位是无效的,不能触及.如果他们*是*,你最终可能会遇到双重自由和其他不良行为. (3认同)
  • @MightyNicM:Rust无法跟踪堆生存期.这在理论上可能是可能的,但我不相信它甚至是假设的计划. (3认同)
  • 重要的区别在于析构函数,而不是堆栈与堆。如果您采用非装箱版本并为 OuterContainer 或 InnerContainer 实现 `Drop`,则不允许部分移动。 (3认同)
  • 一些小的迂腐:编译器实际上确实知道Box,并且历史上已经使你可以做一些你可以用结构到盒子做的事情.我记忆中唯一仍然存在的是你可以移出一个Box的解除引用,这不是别的什么都可以做的.大多数这些东西都被大量回滚为1.0,因为我们没有*希望*编译器给一种类型一堆特殊分析(为什么Rc,Vec等等?).此后,已经做出一些努力来提出一般机制,以原则方式揭示旧的Box分析. (3认同)