[翻译]Rust借用模型-基于栈的借用模型-支持内部可变性

Stacked Borrows: An Aliasing Model for Rust笔记

我们已经看到Stacked Borrows支持它旨在实现的所有转换:在未知代码之间上下移动共享和可变引用的使用。这已经为编译器提供了必要的自由度,可以利用引用类型进行别名分析。
但是要采用Stacked Borrows,我们还需要确保现有Rust代码的大部分实际上与Stacked Borrows的规则兼容。不幸的是,Rust的一个功能与我们对共享引用的处理直接矛盾:内部可变性。与(介绍rust章节)中引入的排除原理(exclusion principle)相反,Rust实际上确实允许在某些受控情况下对共享引用进行突变。例如,Rust提供了一种称为Cell的类型,该类型允许通过与共享引用一起使用的set方法进行改变:

一个 &Celli32<i32>本质上是C中int *的安全版本:可以随时由多方读取和写入。似乎给安全代码访问像Cell这样的类型可能会破坏整个安全性。 Rust的承诺,毕竟取决于排除原则,但Cell受到严格限制以防止出现这种情况。也就是说,Cell只允许将数据复制进出Cell;无法获得对单元格的引用。实质上。单元格排除内部指针。

 

当然,允许对别名数据进行突变会使别名分析像在C语言中一样困难。但是,Rust设计师意识到内部可变性会对否则类型系统提供的非常强大的别名保证造成严重破坏。因此,他们很早就决定程序员必须以一种可以识别的方式“标记”将要属于内部可变性的数据。
由编译器定义。为此,标准库包含“魔术”类型Unsafecell <T>
Unsafecell <T>基本上与T相同;但是,类型是编译器专门识别的。这在共享数据的变异规则中得到了利用,该规则表明共享引用不能用于执行改变,除非改变发生在Unsafecell内部“例如,Cell <T>是围绕Unsafecell <T>构建的新类型。使用安全的API。
我们在堆栈借用中利用此信息,并免除UnsafeCell <T>中的内存
来自共享引用的常规只读限制。为了将引用跟踪到UnsafeCell <T>内部的内存中,我们使用了已经针对原始指针看到的SharedRW项,原始指针和内部可变共享引用都允许可变别名,因此对它们进行类似处理是有意义的。“部分”内部可变性。通常,使之变得复杂的是,共享的参考可以具有“部分”内部可变性。到目前为止,我们已经假装一个引用指向单个位置并影响单个堆栈,但是当然,实际上,像&i32这样的引用跨越4个location。
我们已经看到的所有规则(用于创建引用/原始指针以及对内存访问执行的额外操作)都适用于引用所覆盖的每个位置的堆栈(大小由类型T确定)。但是,对于&(i32,Cell <i32>),前4个字节不在UnsafeCell内,因此要服从完整的Stacked Borrows规则进行共享引用,但是后4个字节位于UnsafeCell内,而Stacked Borrows仅允许对这4个字节的共享数据进行突变。

因此,当创建具有部分“内部可变性”的共享引用时,我们无法对参考覆盖的所有位置执行相同的操作。我们首先选择一个新标签t,然后必须在参考指向的备忘录区域中找到UnsafeCells基本上,这是参考所覆盖的基于类型的内存遍历对于UnsafeCells之外的所有位置,我们按照之前的步骤进行操作,然后在堆栈之后将新的只读SharedRO(t,c)推入堆栈顶部READ-1(请记住,将创建共享引用计为读取访问权限。)对于UnsafeCell内的所有位置,我们改为将读写项SharedRW(tc)添加到堆栈中。
这给了我们与可变原始指针相同的可别名突变.

创建参考并不总是一种访问。与目前为止我们看到的另一个不同之处在于,对于UnsafeCell <T>内部的部分,创建共享引用不会算作读取访问,并且不会在堆栈顶部添加新项(但在中间)


为了解释为什么在涉及内部可变性时不能将创建共享引用作为读取访问的原因,我们必须简要地讨论RefCel1 <T>。 RefCell <T>是Rust Standaro库类型,与Cell <T>有点相似,但是它确实允许内部指针。它通过在运行时跟踪对数据存在多少个可变引用和共享引用,并确保始终存在恰好一个可变引用或任意数量的可变引用来实现安全性。
(只读)共享引用。但是,这意味着可以通过两个引用共享和可变别名的方式从安全代码中调用以下函数:

如果创建一个共享引用(第3行中的重新借项,实际上已经在第2行中的重新标记)被计为读取访问,那将违反可变的唯一性!毕竟,可变引用的一个重要属性是在使用引用时对该内存没有其他访问(读取或写入)。我们也不能在堆栈顶部为more_shared添加新项目;这将违反不变的标签,即可变标签位于它指向的堆栈顶部(重新标记后)的不变性,这种不变性在我们的证明草图中至关重要。
因此,我们需要创建一个共享的引用,而不必将所有内容从堆栈中弹出。但是,我们仍然希望该项目在其派生item的“旁边”。因此,当从Pointer(location,t)创建新的共享引用时,我们将新的SharedRW添加到堆栈中t的上方。

 

规则(NEW-SHARED-REF-2)。当从一些现有的指针值Pointer(location,t)创建一个新的共享引用时,我们创建一个新的标签t',并使用Pointer(location,t')作为共享引用的值。
对于此引用覆盖的Unsafecell内部的所有位置,我们找到用于borrowed stack的item,并在该项目上方添加SharedRW(t')。 (如果没有这个item,则程序具有未定义的行为)对于其余位置,这将算作具有旧标记t的读取访问权限(请参阅READ-1)。然后我们将SharedRO(t')推到栈顶。
为了使具有内部可变性和可变原始指针的共享引用的规则保持一致(毕竟都使用SharedRW),我们将后者调整为也不算作访问:

 

规则(NEW-MUTABLE-RAW-2)。当通过强制转换创建可变的原始指针时(expr为mut T)
从某个具有可变值Pointer(location,t)的可变引用(&mut T)中,我们在借用堆栈中找到t的项目,然后在该项目的上方添加SharedRW(L)。 (如果没有这样的项目,则程序具有未定义的行为。)然后,指针获得值Pointer(location。L)
使用这些规则,重新借用共享只会在堆栈中间添加一些SharedRW,
因此,即使别名是可变的,我们仍然保持属性,即堆栈的顶部是唯一的,且具有与可变相同的标签。这足以使我们的证明工作正常,并且还允许使用别名指针的程序调用evil_ref_cell。
但是,这意味着我们可以在堆栈中以许多彼此相邻的方式共享SharedRW,
2.g.,当从同一可变引用创建一堆具有内部可变性的共享引用时。正如我们认为堆栈顶部的异常SharedRO可以使用一个“组”项而无需彼此从堆栈中移走一样,我们也希望对相邻的SharedRW项的“组”执行相同的操作。因此,我们需要调整(读取和写入)的规则以这样一种方式进行访问:如果使用了SharedRW的相邻组中的任何一个,则该组中的其他组都保留在堆栈中。
对于写操作,其外观如下(也包含PROTECTOR):

 

规则(WRITE-1)。在使用指针值Pointer(,t)的任何写访问中,对受该访问影响的每个位置l执行以下操作:弹出堆栈,直到顶层项为Unique(t,_)或其中存在SharedRW(t,_)堆栈中相邻SharedRW的顶部“组”(即SharedRW(t,)
在堆栈中,并且上方只有SharedRW)。如果这是不可能的,或者使用活动的保护程序弹出该项目,则该程序具有未定义的行为。
读取访问权限和禁用的项目。读取访问也需要考虑到我们现在已经标记了SharedRW项目的事实。我们希望能够从SharedRW项中读取数据,而不会使与其相邻的其他SharedRW或SharedRO项无效。 (这与我们在READ-1中读取SharedRO项和在WRITE-1中写入SharedRW项的操作类似。)
但是,我们仍然需要确保在项目上方没有剩余任何唯一项目来证明访问的正确性,并且我们的操作方式略有不同。由于种种原因,我们将简要说明,而不是将项目从堆栈中弹出,而是将所有项目保留在原处,但将“唯一”项目标记为“已禁用”。这尤其意味着从SharedRW读取将保持堆栈上位于其上方的所有SharedRW项的有效性(而不仅仅是与其相邻的项)。
为此,我们引入了一种新的item

并且我们将读取规则更改如下:

 

规则(READ-2)。在使用指针值Pointer(_,t)的任何读取访问中,对受该访问影响的每个位置l执行以下操作:在堆栈中找到标记为t的最顶部(非禁用)项
(如果t = L,则可能有多个)。将其上方的所有Unique(,)替换为Disabled。如果这些Unique ..中的任何一个具有活动保护器,则程序具有未定义的行为。
在READ-2中出现这种怪异的原因是,使用一些真实的Rust代码进行测试后很快发现了以下模式。这种模式得到了广泛的使用(因此不应引起未定义的行为),但是如果没有“禁用”的方法,它将导致未定义的行为:

 

在第7行之前,l的借用堆栈包含在x和raw_ptr之间创建的用于中间可变引用的项目:由于将引用传递给Rust中的函数隐式地重新借入而引入的临时变量,以及当在make_raw中重新标记时选择了问题。麻烦在于,从x读取必须使其上方的唯一项无效:example3_down转换在函数调用之间进行向下写入操作依赖于以下事实:其他代码无法从可变引用覆盖的内存中读取不会引起违规。但是,如果我们被迫遵守堆栈规范,则只能通过首先弹出SharedRW(L)来使唯一性无效,这会使第9行变为非法,因为SharedRW(L)不再位于堆栈中。

 

要解决此问题”,我们在处理读取访问时删除标记的方式上偏离了堆栈原则。即,不要从堆栈中弹出项目,直到被访问的项目上方没有更多的Unique为止(这会弹出一些SharedRW ),我们只是禁用了被访问项上方的“唯一”,而只保留了SharedRW项。这样,SharedRW项不会因“唯一”项的弹出而遭受任何“附带损害”,并且允许使用上述编程模式。
这是我们需要的最后一个调整。现在让我们退后一步,从形式上完整地考虑语义。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值