我正在寻找一种方法,如何说服借用检查人员以下代码是安全的。
SliceHolder是一个结构,我无法更改它的接口(不同的库)。我想在 中临时设置切片SliceHolder,调用一些方法来处理数据,然后完成借用(停止借用切片)。我的问题是,借阅检查器不允许我这样做。如果我添加到接口foo where 'a: 'b借用检查器将失败,self.slice = slice;因为输入切片无法存储(即使是临时的)到结构中,寿命将更长(至少我认为这是原因)。如果我更改为where 'b: 'a借用检查器将失败x[0] = 5,因为它认为该切片的借用时间比 SliceHolder 的生命周期长。
我只是想临时使用切片的引用,并在函数foo结束时以某种方式释放该引用,以便借用检查器不会将引用视为已使用。为此,我使用了self.slice = &DEFAULT;应该说服slice不再存储在内部的借用检查器SliceHolder,但这不起作用。
我已经找到了不安全和原始指针(self.slice = unsafe {&*(slice as *const [u8])};)的解决方案,但我认为这不是解决 Rust 中这个问题的正确方法。
struct SliceHolder<'a> {
slice: &'a [u8],
}
const DEFAULT: [u8; 0] = [];
impl<'a> SliceHolder<'a> {
fn foo<'b>(&mut self, slice: &'b [u8]) -> Vec<u8> {
self.slice = slice; // FIRST POSITION WHERE BORROW CHECKER COMPLAINS
let result = do_something_with_holder(&self);
self.slice = &DEFAULT;
result
}
}
// blackbox function, do some computation based on the slice
fn do_something_with_holder(holder: &SliceHolder) -> Vec<u8> {
Vec::from(holder.slice)
}
fn main() {
let mut holder = SliceHolder {
slice: &DEFAULT,
};
let mut x: [u8; 1] = [1];
holder.foo(&x);
x[0] = 5;
holder.foo(&x); // SECOND POSITION WHERE BORROW CHECKER COMPLAINS
}
Run Code Online (Sandbox Code Playgroud)
尝试使用 Rust 1.40。
当尝试解决此类生命周期问题时,绘制所有涉及的生命周期图表会很有帮助。有 2 个命名生命周期,但至少有 5 个相关生命周期。这只是为了功能SliceHolder::foo。
'static。这是将会有的生命周期&DEFAULT(由于'static提升)。'a。附加到 类型的生命周期self。'b。切片的生命周期。'c)。&mut借用的生命周期self。'd)。与函数体相对应的生命周期。这是它们关系的图表(请原谅蹩脚的 ASCII 艺术)。图中较高的生命周期通常持续时间较长,但只有当有一系列箭头连接它们时,一个生命周期才会比另一个生命周期长。
'static
^
/ \
/ \
/ \
| |
V V
'a 'b
| |
V |
'c |
\ /
\ /
\ /
V
'd
Run Code Online (Sandbox Code Playgroud)
添加生命周期'b: 'a界限相当于在该图上添加一个箭头'a -> 'b,表示'aoutlives'b或具有生命周期的借用'a在整个 period 内有效'b。
让我们看一下这些生命周期如何在main.
// holder: SliceHolder<'a>, where 'a: 'static
let mut holder = SliceHolder { slice: &DEFAULT };
let mut x: [u8; 1] = [1];
// Take a mutable borrow of holder (with lifetime 'c1)
// Also take a borrow of x with lifetime 'b1
holder.foo(&x);
// mutate x. This forces 'b1 to end no later than here.
x[0] = 5;
// Take another mutable borrow of holder ('c2)
// and another borrow of x ('b2)
holder.foo(&x);
Run Code Online (Sandbox Code Playgroud)
这表明我们不能有'b: 'a,因为这意味着'a必须不晚于'b结束时结束。这将迫使在突变holder之前停止存在。x但holder用得晚一些。
所以我们已经证明我们不能拥有'b: 'a. 那还剩下什么?当我们执行赋值时self.slice = slice;,我们隐式地从&'b [u8]to进行转换&'a [u8]。这需要'b: 'a我们刚刚排除的。self总是必须有 type SliceHolder<'a>,所以我们不能仅仅缩短它的生命周期。
旁白:如果我们不坚持 in 中的切片(至少)
self总是'a有生命周期,我们可能会在某个时刻(例如 indo_something_with_holder)出现恐慌,并避免将控制路径self.slice重新分配给具有更长生命周期的东西。'b将结束(invalidatingslice: &'b [u8]),但self仍然存在并持有无效引用。如果您使用过代码,这些都是您需要考虑的事情unsafe。
然而,我们可以有第二个变量,其生命周期较短,其值(即内部切片)与 相同self。我们需要第二个变量的类型的SliceHolder<'_>生命周期不长于'a(以便我们可以使用self的引用)并且不长于'b(以便我们可以分配slice给它的切片)。我们看图可以看到,确实有一个比'a和都短的生命周期'b,即'd。
幸运的是,我们不需要担心这一生的命名。重要的是它存在,编译器会解决剩下的问题。那么我们如何获得第二个变量呢?我们需要以某种方式self进入一个新变量。但请记住,我们无法移出可变引用,因此我们必须在内部保留一些有效的内容。
我们已经计划更换self的参考&DEFAULT,那么为什么不这样做呢?相关命令是std::mem::replace并且可以像 一样使用let second_variable = std::mem::replace(self, SliceHolder {slice: &DEFAULT})。
有一些稍微更符合人体工程学的方法可以做到这一点,但它需要为SliceHolder. 如果SliceHolder真的只包含一个引用,它可以实现Copy,这使得这种情况下的一切变得更容易。除此之外,实现Default它(作为&DEFAULT默认切片)将允许您使用新稳定的std::mem::take而不是std::mem::replace. 那么您就不需要内联构造默认值。
可能还有其他一些事情需要考虑,但如果没有最小可重复示例,很难说是什么。我会给你一些工作代码(游乐场)。
struct SliceHolder<'a> {
slice: &'a [u8],
}
const DEFAULT: [u8; 0] = [];
struct Result;
fn do_something_with_holder(_: &SliceHolder) -> Result {
Result
}
impl<'a> SliceHolder<'a> {
fn foo<'b>(&mut self, slice: &'b [u8]) -> Result {
// new_holder has the exact value that self would have had before
let mut new_holder: SliceHolder<'_> =
std::mem::replace(self, SliceHolder { slice: &DEFAULT });
new_holder.slice = slice;
let result = do_something_with_holder(&new_holder);
// self.slice = &DEFAULT; // no longer needed - we've already taken care of that
result
}
}
fn main() {
let mut holder = SliceHolder { slice: &DEFAULT };
let mut x: [u8; 1] = [1];
holder.foo(&x);
x[0] = 5;
holder.foo(&x);
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
364 次 |
| 最近记录: |