移动语义对 Rust 中的引用透明性意味着什么?

What do move semantics imply for referential transparency in Rust?

我正在尝试弄清楚移动语义如何影响引用透明度。

Referential transparency (RT) allows us to replace any expression with its result without changing the meaning of the program (paraphrased from Functional Programming in Scala)。例如,我可以将程序中的任何地方的 1 + 1 替换为 2,并且不会有任何改变。这个 Python 程序是引用透明的:

@dataclass
class Bucket:
    things: List[str]

leaves = ["leaves"]

def bucket_with_sand(things: List[str]) -> Bucket:
    return Bucket(things + ["sand"])

bucket_with_sand(leaves)  # can be replaced with Bucket(["leaves", "sand"]) with no change to the program

而这个函数在适当的地方改变了它的参数

def bucket_with_sand(things: List[str]) -> Bucket:
    things += ["sand"]
    return Bucket(things)

所以用结果替换函数调用改变了意思。它不再是引用透明的。在带有 的语言中,我们可以通过移动 leaves 来避免这个问题(并依赖于 Vec 是非 Copy 的事实):

struct Bucket {
    things: Vec<&str>,
}

let leaves = vec!["leaves"];

fn bucket_with_sand(things: Vec<&str>) -> Bucket {
    things.push("sand");
    Bucket { things }
}

bucket_with_sand(leaves); // mutates `things`
// doesn't matter that `leaves` has been mutated here as it's now out of scope

这似乎又是引用透明的了。这个对吗?这些举措是否放松了对 RT 设计的传统限制?还是移动不具有参考透明性?我特别想知道 RT 是否有我未曾见过的更广泛的影响。

引用透明的概念在几乎所有在真实计算机上执行的语言中都有点模糊,尤其是在具有命令式状态的语言中,Rust 也不例外。调用可能会产生副作用——从执行 IO 到 运行 内存不足,再到仅仅改变一个可变变量——这取决于你是否包括那些你可能会考虑的 "nothing changing"函数是非引用透明的。它们不是纯数学函数,而是调用时确实会改变世界状态的过程。

也就是说:所谓的 "ownership" Rust 系统——它是 "affine" 或 "move" 类型与其 multi-reader/single-writer 借用系统的组合——用于显着减少程序中可能产生的副作用。特别是它(主要*)消除了大多数其他命令式语言中最普遍和有害的副作用:可变别名。也就是说,在 Rust 中,你(大多数*)永远不会有两个或更多对同一内存位置的引用,其中一个函数中的一个引用会改变内存位置作为 运行 的副作用,而另一个引用在某些其他函数只看到内存位置 "suddenly change" 中的值。这意味着任何时候一个值将要发生变化,它将通过其 唯一当前引用 发生变化——一个 &mut 或一个拥有变量——这意味着正如你在这里问的那样,在 某种程度上 关于引用透明性的假设在 Rust 中比在大多数其他命令式语言中更有可能成为现实。

上面的“(mostly*)”星号指出了一个更大的例外:不安全的代码可能会违反此规则,并且在几个库函数中也是如此。例如 portion of Rust's standard library that provides so-called "interior mutability" furnishes an unsafe cell type 以及强制禁止可变别名的包装类型 动态地 ,以时间方式:一个这样的可变访问可以发生 在给定时间,但允许它们按顺序从不同的共享引用按顺序发生。

同样的警告适用于几乎所有真实的语言,无论 "pure" 它如何推销自己:ML 家族有 ref 个单元,Haskell 有它的 unsafe library functions, Lisps 有 set! 等等。这些都是对以下事实的让步:有时能够通过数学抽象(函数式语言中的纯值,或 Rust 中的仿射值)到达具有不受限制的可变别名的底层机器,从而具有压倒性的性能优势。