RUST 所有权移动问题

2023-10-01 03:46:06 +08:00
 caobug

刚随手写了一个示例,尝试探测编译器检测机制。以下代码在 JAVA/C/C++等众多语言中没有任何问题,即使按照 RUST 的所有权原则,也应该正常。但实际上编译失败了。

struct Dog {
    name: String,
}

impl Dog {
    fn release(self) {
        println!("{}", self.name)
    }
}

struct Person {
    dog: Dog,
    name: String,
}

fn main() {
    let mut person = Person {
        dog: Dog {
            name: String::from("Hamel"),
        },
        name: String::from("Zoe"),
    };

    person.dog.release();

    for _ in 0..1 {
        person.dog = Dog {
            name: String::from("Hogge"),
        };
    }

    println!("{} got a new dog: {}", person.name, person.dog.name);
}

以上代码我先通过 Person 关联了 Dog ,随后释放 Person 中的 Dog ,到此 Dog 不再有效,若后续直接调用会编译失败。

我跟着创建一个百分比会执行的 for 循环重新赋值,按理说指针已发生了变更,后续在循环外使用的 Dog 是新建的对象不会有问题。

但事上上编译失败。Rust 的检测器似乎没有认真评估 for 中的条件,认为 loop 至少执行一次,而 for 却不确定。当我把 for 改成 loop ,它确实正常了。

loop {
    person.dog = Dog {
        name: String::from("Hogge"),
    };
    break
}

我接着在 loop 中加一个百分比会执行的条件判断,最终仍然失败了。

loop {
    if true {
        person.dog = Dog {
            name: String::from("Hogge"),
        };
    }
    break
}

如果说写 C/C++满脑袋都是寄存器、堆、栈、指针...,那么 RUST 必然是所有权检查器。

2188 次点击
所在节点    Rust
24 条回复
DianQK
2023-10-10 22:29:39 +08:00
@owtotwo #20
除了 RFC 和具体代码我还没看到,剩下的我应该基本了解。如果先做一些常量传播,再做借用检查就可以了(但是常量传播可能也需要先有一个合格的借用检查?)
caobug
2023-10-11 05:08:53 +08:00
@owtotwo 非常感谢,你解释的非常清楚远超 RUST 指南,恳请抽空出些 RUST 文章。
owtotwo
2023-10-11 15:07:30 +08:00
@DianQK #21
嗯呐 这个确实就是关键所在

若我没记错的话 似乎 rustc 的编译流程会有两次的常量折叠/传播 一次是在前端的 MIR 中 另一次是在后端如 LLVM 中
(好像是前端优化一次能降低给后端的 IR 代码复杂度)

MIR 中支持常量传播应该是比较早前的事了(或许有相关公告) 似乎是支持控制流的(代码可能在 mir 部分的 const_prop.rs ?文件名应该长得差不多)
但并不知道是否支持“消掉 if const_expr”的行为
(我不知道这种分支优化的术语应该是什么 死代码消除 Dead Code Elim ?或者是叫 Sparse Cond Const Prop ?中文可能是 稀疏条件常量传播 之类的 或许也不准确)

但比较尴尬的是 常量传播是在 MIR 的优化阶段进行的 而 borrowck 是在 mir-opt 之前进行的(如果我没记错的话)

所以正如老哥你所说的 常量传播时应该已经有借用检查了
(以及我感觉理论上应该确实是能在借用检查前算 const 的 就是不知道最终会不会增加 MIR 部分编译的总耗时)

编译流程层面的改动影响对 rustc 而言还是挺大的(如 Polonius 也只是 borrowck 部分的平替) 所以短期内可能不会有相应优化了(个人感觉 不知道目前有没有人提对应的 RFC )


以上的话并不严谨 我也暂时没能去进行校验 或许会有些错漏或过时(记忆有点旧了)

有条件的朋友或许可以补充下相关链接~
owtotwo
2023-10-11 15:37:51 +08:00
@caobug #22
有讲清楚了就好 能对别人有帮助还是很开心的 : )

Rust 文章的话 不太好写 写起来时很难兼顾到不同熟练度的 Rust 小伙伴(主要还是我自己能力有限)

举个例子
就像一开始其实我想**直接**用 `while true {}`和`loop {}`为什么不一样 来解释这问题的(前者迭代 0+次 后者 1+次)
但是这样会引入题目中没提到的 while 语句 以及它们另外的差异( loop-break <value>能返回值而 while 恒为`()`之类的) 最后还得再迁移到 for 语句来解释

这样就有可能将问题复杂化了 而如果跳过中间例子直接说“const_expr 在 borrowck 阶段不求值” 不熟悉的小伙伴有可能一下子转不过来

且问题涉及 if 语句 所以最后决定用`if false {}`作例子来渐进地解释 比较好理解

Rust 文章同理 讲一个点 从多浅讲到多深 我就有点犯难了
有的知识点实在涉及太多 比如 Pin 感觉没十几页纸实在讲不清楚 一想就头都大了 0.o

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/978382

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX