我正在寻找一种方法,如何说服借用检查人员以下代码是安全的。
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
,表示'a
outlives'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 次 |
最近记录: |