我正在寻找一种方法,如何说服借入检查器,以下代码是安全的。
情况
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])};)的解决方案,但我认为这不是解决这个问题的正确方法。
代码
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
}已尝试使用rust 1.40。
发布于 2020-01-27 08:17:05
在尝试解决这样的生命周期问题时,用图表表示所有涉及的生命周期是很有帮助的。有2个命名生命周期,但至少有5个相关生命周期。这只适用于函数SliceHolder::foo。
'static.这是由于'static promotion).'a.,&DEFAULT将具有的生命周期附加到self.'b.类型的生存期slice.'c)。self.&mut借用的生命周期(我称之为'd)。与函数体对应的生存期。这是他们之间关系的图表(请原谅糟糕的ASCII艺术)。图中较高的生命周期通常持续更长时间,但只有在有一系列箭头连接它们的情况下,一个生命周期才会比另一个生命周期长。
'static
^
/ \
/ \
/ \
| |
V V
'a 'b
| |
V |
'c |
\ /
\ /
\ /
V
'd添加生命周期界限'b: 'a相当于在此图上添加了一个箭头'a -> 'b,说明'a比'b寿命更长,或者具有生命周期'a的借用在整个周期'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);这表明我们不能使用'b: 'a,因为这意味着'a必须在'b结束之前结束。这将迫使holder在x发生突变之前停止存在。但在此之后才使用holder。
所以我们已经证明了我们不能有'b: 'a。那还剩下什么呢?当我们执行赋值self.slice = slice;时,我们隐式地从&'b [u8]转换为&'a [u8]。这需要'b: 'a,我们刚刚排除了这一点。self总是必须有类型SliceHolder<'a>,所以我们不能简单地缩短它的生命周期。
抛开
不谈:如果我们不坚持
self中的切片始终具有生存期(至少)'a,我们可能会在某些时候(例如在do_something_with_holder中)感到恐慌,并避免self.slice被重新分配到生命周期更长的东西的控制路径。'b将结束(使slice: &'b [u8]无效),但self将仍然存在并持有无效的引用。如果您曾经使用过unsafe代码,这些是您需要考虑的事情。
然而,我们可以有一个生命周期更短的第二个变量,它的值(即内部切片)与self相同。我们需要第二个变量让类型SliceHolder<'_>的生命周期不长于'a (这样我们就可以使用self的引用),也不能长于'b (这样我们就可以将slice赋值给它的片)。我们在图中看到确实存在一个比'a和'b都短的生命周期,即'd。
幸运的是,我们不需要担心这一生的命名。重要的是它的存在,编译器会解决剩下的问题。那么我们如何得到第二个变量呢?我们需要以某种方式将self移到一个新变量中。但请记住,我们不能移出可变引用,所以我们必须在内部保留一些有效的引用。
我们已经计划在&DEFAULT中去掉self的引用了,为什么不这样做呢?相关的命令是std::mem::replace,可以像let second_variable = std::mem::replace(self, SliceHolder {slice: &DEFAULT})一样使用。
有一些稍微符合人体工程学的方法可以做到这一点,但这将涉及到为SliceHolder添加一些特性。如果SliceHolder真的只包含一个引用,它可以实现Copy,这使得这种情况下的所有事情都变得更容易。除此之外,为它实现Default (使用&DEFAULT作为默认切片)将允许您使用新稳定的std::mem::take而不是std::mem::replace。这样你就不需要构造内联的默认值了。
可能还有其他一些事情需要考虑,但如果没有一个最小的可重现的例子,就很难说出什么了。我将为您留下一些有效的代码(playground)。
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);
}https://stackoverflow.com/questions/59923212
复制相似问题