【问题标题】:What do move semantics imply for referential transparency in Rust?移动语义对 Rust 中的引用透明意味着什么?
【发布时间】:2020-04-28 23:36:47
【问题描述】:

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

Referential transparency (RT) 允许我们用它的结果替换任何表达式而不改变程序的含义(从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)

所以用结果替换函数调用会改变含义。它不再是引用透明的。在带有move semantics like Rust's 的语言中,我们可以通过移动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 != haskell,不要仅仅为此提供向量的所有权,这不是 Rust 的方式)
  • @Stargateur 您能否详细说明“这不是 Rust 的方式”。为什么不是这样?
  • 特别是,它似乎是一条非常简洁的 FP 途径,而无需“处理”副作用
  • 我觉得和纯函数的概念有关

标签: functional-programming rust move-semantics referential-transparency


【解决方案1】:

在几乎所有在真实计算机上执行的语言中,引用透明的概念都有些模糊,尤其是在具有命令式状态的语言中,Rust 也不例外。调用可能会产生副作用——从执行 IO 到内存不足再到仅仅改变一个可变变量——并且取决于您是否将那些包含在“没有改变”的意义上的那些包括在内,您可能会认为函数是非参照透明。它们不是纯粹的数学函数,而是在调用时会改变世界状态的过程。

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

上面的“(mostly*)”星号指出了一个更大的例外:不安全的代码可能违反此规则,并且在几个库函数中也如此。例如,提供所谓“内部可变性”的portion of Rust's standard library 提供了一个unsafe cell type 以及以时间方式动态强制禁止可变别名的包装器类型:一种这样的可变访问可以在给定时间发生,但它们可以从不同的共享引用依次发生。

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

【讨论】:

    猜你喜欢
    • 2019-08-13
    • 1970-01-01
    • 2021-06-18
    • 1970-01-01
    • 2022-12-12
    • 2011-12-28
    • 2020-10-12
    • 2011-06-11
    • 2010-10-02
    相关资源
    最近更新 更多