简体   繁体   English

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

[英]What do move semantics imply for referential transparency in Rust?

I'm trying to work out how move semantics affect referential transparency.我正在尝试弄清楚移动语义如何影响引用透明度。

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 ).引用透明(RT) 允许我们在不改变程序含义的情况下用其结果替换任何表达式(从Scala 中的函数式编程转述)。 For example, I can replace 1 + 1 anywhere in my program with 2 , and nothing should change.例如,我可以代替1 + 1 ,在我的程序的任何地方2 ,并没有什么应该改变。 This Python program is referentially transparent:这个 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

whereas this function mutates its argument in place而这个函数就地改变了它的参数

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

so replacing the function call with its result changes the meaning.因此用其结果替换函数调用会改变含义。 It's no longer referentially transparent.它不再具有引用透明性。 In a language with move semantics like Rust's , we can avoid this problem by moving leaves (and relying on the fact that Vec is non- Copy ):像 Rust 这样具有移动语义的语言中,我们可以通过移动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

This appears to be referentially transparent again.这似乎再次具有参考透明性。 Is this correct?这样对吗? Do such moves relax conventional constraints on RT design?这些举措是否放松了对 RT 设计的传统限制? Or are moves not referentially transparent?或者动作不是参照透明的? I'm particularly interested to know if there are there broader implications on RT that I've not seen.我特别想知道是否对 RT 有更广泛的影响,我还没有看到。

The concept of referential transparency is a bit blurry in almost all languages that execute on real computers, especially in languages with imperative state, and Rust is no exception to this fact.在几乎所有在真实计算机上执行的语言中,引用透明的概念都有些模糊,尤其是在具有命令式状态的语言中,Rust 也不例外。 A call can have a side effect -- anything from performing IO to running out of memory to merely mutating a mutable variable -- and depending on whether you include those in your sense of "nothing changing" you might well consider functions to be non-referentially-transparent.调用可能会产生副作用——从执行 IO 到内存不足,再到仅仅改变一个可变变量——根据你是否包括那些你认为“没有变化”的函数,你可能会认为函数是非-参照透明。 They are not pure mathematical functions, but procedures that do change the state of the world when called.它们不是纯数学函数,而是在调用时确实会改变世界状态的过程。

That said: the so-called "ownership" system of Rust -- its combination of "affine" or "move" types with its multi-reader/single-writer borrowing system -- serves to dramatically reduce the set of possible side effects in a program.也就是说:Rust 所谓的“所有权”系统——它的“仿射”或“移动”类型与其多读者/单作者借用系统的组合——用于显着减少可能的副作用一个程序。 In particular it (mostly*) eliminates the most pervasive and pernicious side effect in most other imperative languages: mutable aliasing .特别是它(主要是*)消除了大多数其他命令式语言中最普遍和最有害的副作用:可变别名 That is, in Rust you will (mostly*) never have two or more references to the same memory location, where one reference in one function mutates the memory location as a side effect of running, and the other reference in some other function just sees the value in the memory location "suddenly change".也就是说,在 Rust 中,您(大多数情况下*)永远不会有两个或多个对同一内存位置的引用,其中一个函数中的一个引用会改变内存位置作为运行的副作用,而另一个函数中的另一个引用只会看到内存位置中的值“突然改变”。 This means that any time a value is going to be mutated, it will be mutated through its sole current reference -- either a &mut or an owning variable -- and this means that, as you asked here, to a certain degree assumptions about referential transparency have a greater chance of being true in Rust than they do in most other imperative languages.这意味着任何时候一个值要发生变异,它都会通过其唯一的当前引用发生变异——要么是&mut要么是一个拥有变量——这意味着,正如你在这里问的那样,在某种程度上,关于引用的假设透明性在 Rust 中比在大多数其他命令式语言中更真实。

The "(mostly*)" asterisk above calls out a further relatively large exception: unsafe code can violate this rule, and does in several library functions.上面的“(mostly*)”星号说明了另一个相对较大的异常:不安全的代码可能违反此规则,并且在几个库函数中也是如此。 For example the portion of Rust's standard library that provides so-called "interior mutability" furnishes an unsafe cell type as well as wrapper types that enforce the prohibition on mutable aliasing dynamically , in a temporal fashion: one such mutable access can happen at a given time but they are allowed to happen sequentially from different shared references in sequence.例如,Rust 标准库中提供所谓的“内部可变性”的部分提供了一种不安全的单元格类型以及包装器类型,它们以时间方式动态地强制禁止可变别名:一个这样的可变访问可以发生在给定的时间,但允许它们从不同的共享引用依次发生。

The same sort of caveat applies to almost every real language, no matter how "pure" it markets itself: the ML family has ref cells, Haskell has its unsafe library functions , Lisps have set!同样的警告适用于几乎所有真正的语言,无论它推销自己有多“纯粹”:ML 系列有ref单元,Haskell 有不安全的库函数,Lisps 已经set! and so forth.等等。 These are all concessions to the fact that sometimes there's an overwhelming performance advantage to being able to reach through the mathematical abstraction (of pure values in functional languages, or of affine values in Rust's case) to the underlying machine with its unrestricted mutable aliasing.这些都是对这样一个事实的让步:有时能够通过数学抽象(函数语言中的纯值,或 Rust 中的仿射值)到达具有不受限制的可变别名的底层机器,具有压倒性的性能优势。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM