[英]What do move semantics imply for referential transparency in Rust?
我正在嘗試弄清楚移動語義如何影響引用透明度。
引用透明(RT) 允許我們在不改變程序含義的情況下用其結果替換任何表達式(從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)
因此用其結果替換函數調用會改變含義。 它不再具有引用透明性。 在像 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
這似乎再次具有參考透明性。 這樣對嗎? 這些舉措是否放松了對 RT 設計的傳統限制? 或者動作不是參照透明的? 我特別想知道是否對 RT 有更廣泛的影響,我還沒有看到。
在幾乎所有在真實計算機上執行的語言中,引用透明的概念都有些模糊,尤其是在具有命令式狀態的語言中,Rust 也不例外。 調用可能會產生副作用——從執行 IO 到內存不足,再到僅僅改變一個可變變量——根據你是否包括那些你認為“沒有變化”的函數,你可能會認為函數是非-參照透明。 它們不是純數學函數,而是在調用時確實會改變世界狀態的過程。
也就是說:Rust 所謂的“所有權”系統——它的“仿射”或“移動”類型與其多讀者/單作者借用系統的組合——用於顯着減少可能的副作用一個程序。 特別是它(主要是*)消除了大多數其他命令式語言中最普遍和最有害的副作用:可變別名。 也就是說,在 Rust 中,您(大多數情況下*)永遠不會有兩個或多個對同一內存位置的引用,其中一個函數中的一個引用會改變內存位置作為運行的副作用,而另一個函數中的另一個引用只會看到內存位置中的值“突然改變”。 這意味着任何時候一個值要發生變異,它都會通過其唯一的當前引用發生變異——要么是&mut
要么是一個擁有變量——這意味着,正如你在這里問的那樣,在某種程度上,關於引用的假設透明性在 Rust 中比在大多數其他命令式語言中更真實。
上面的“(mostly*)”星號說明了另一個相對較大的異常:不安全的代碼可能違反此規則,並且在幾個庫函數中也是如此。 例如,Rust 標准庫中提供所謂的“內部可變性”的部分提供了一種不安全的單元格類型以及包裝器類型,它們以時間方式動態地強制禁止可變別名:一個這樣的可變訪問可以發生在給定的時間,但允許它們從不同的共享引用依次發生。
同樣的警告適用於幾乎所有真正的語言,無論它推銷自己有多“純粹”:ML 系列有ref
單元,Haskell 有不安全的庫函數,Lisps 已經set!
等等。 這些都是對這樣一個事實的讓步:有時能夠通過數學抽象(函數語言中的純值,或 Rust 中的仿射值)到達具有不受限制的可變別名的底層機器,具有壓倒性的性能優勢。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.