移动语义对于 Rust 中的引用透明性意味着什么? [英] What do move semantics imply for referential transparency in Rust?

查看:57
本文介绍了移动语义对于 Rust 中的引用透明性意味着什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试研究移动语义如何影响引用透明度.

I'm trying to work out how move semantics affect referential transparency.

参照透明 (RT) 允许我们用其结果替换任何表达式而不改变程序的含义(意译来自 Scala 中的函数式编程).例如,我可以用 2 替换我程序中任意位置的 1 + 1,并且没有任何变化.这个 Python 程序是引用透明的:

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). For example, I can replace 1 + 1 anywhere in my program with 2, and nothing should change. This Python program is referentially transparent:

@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)

因此用其结果替换函数调用会改变含义.它不再具有引用透明性.在具有移动语义如 Rust 的语言中,我们可以通过移动 leaves(和依赖于 Vec 是非 Copy) 的事实:

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):

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 有更广泛的影响,我还没有看到.

This appears to be referentially transparent again. Is this correct? Do such moves relax conventional constraints on RT design? 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.

推荐答案

在几乎所有在真实计算机上执行的语言中,引用透明的概念都有些模糊,尤其是在具有命令式状态的语言中,Rust 也不例外事实.调用可能会产生副作用——从执行 IO 到内存不足,再到仅仅改变一个可变变量——根据你是否包括那些你认为没有变化"的函数,你可能会认为函数是非-参照透明.它们不是纯数学函数,而是在调用时确实会改变世界状态的过程.

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. 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. They are not pure mathematical functions, but procedures that do change the state of the world when called.

也就是说:Rust 所谓的所有权"系统——它的仿射"或移动"类型与其多读者/单作者借阅系统的组合——用于显着减少可能的集合程序中的副作用.特别是它(主要是*)消除了大多数其他命令式语言中最普遍和最有害的副作用:可变别名.也就是说,在 Rust 中,您(大多数情况下*)永远不会有两个或多个对同一内存位置的引用,其中一个函数中的一个引用会改变内存位置作为运行的副作用,而另一个函数中的另一个引用只会看到内存位置中的值突然改变".这意味着任何时候一个值要被改变,它都会通过它的唯一当前引用改变——要么是一个 &mut 要么是一个拥有变量——和这意味着,正如您在此处询问的那样,在 一定程度上,关于引用透明性的假设在 Rust 中比在大多数其他命令式语言中更有效.

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. 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". 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.

上面的(mostly*)"星号说明了另一个相对较大的例外:不安全的代码可能违反此规则,并且在几个库函数中也是如此.例如,Rust 标准库的一部分,它提供了所谓的内部可变性"提供了一个不安全的单元格类型以及包装器以时间方式动态强制禁止可变别名的类型:这样的可变访问可以在给定时间发生,但允许它们从不同的共享引用顺序发生顺序.

The "(mostly*)" asterisk above calls out a further relatively large exception: unsafe code can violate this rule, and does in several library functions. 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.

同样的警告适用于几乎所有真实的语言,无论它推销自己有多纯粹":ML 系列有 ref 单元,Haskell 有它的 不安全的库函数,Lisps 有 set! 等等.这些都是对这样一个事实的让步:有时能够通过数学抽象(函数语言中的纯值,或 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! 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 中的引用透明性意味着什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆