不安全 Rust 中的引用堆栈,但确保不安全性不会泄漏到堆栈之外?
Stack of references in unsafe Rust, but ensuring that the unsafeness does not leak out of the stack?
我正在实现一些递归代码,其中调用堆栈中更深层的函数实例可能需要引用先前帧中的数据。但是,我只能对这些数据进行非 mut 访问,因此我会收到这些数据作为参考。因此,我需要在堆栈数据结构中保留对这些数据的引用,以便可以从更深的实例访问。
举例说明:
// I would like to implement this RefStack class properly, without per-item memory allocations
struct RefStack<T: ?Sized> {
content: Vec<&T>,
}
impl<T: ?Sized> RefStack<T> {
fn new() -> Self { Self{ content: Vec::new() } }
fn get(&self, index: usize) -> &T { self.content[index] }
fn len(&self) -> usize { self.content.len() }
fn with_element<F: FnOnce(&mut Self)>(&mut self, el: &T, f: F) {
self.content.push(el);
f(self);
self.content.pop();
}
}
// This is just an example demonstrating how I would need to use the RefStack class
fn do_recursion(n: usize, node: &LinkedListNode, st: &mut RefStack<str>) {
// get references to one or more items in the stack
// the references should be allowed to live until the end of this function, but shouldn't prevent me from calling with_element() later
let tmp: &str = st.get(rng.gen_range(0, st.len()));
// do stuff with those references (println is just an example)
println!("Item: {}", tmp);
// recurse deeper if necessary
if n > 0 {
let (head, tail): (_, &LinkedListNode) = node.get_parts();
manager.get_str(head, |s: &str| // the actual string is a local variable somewhere in the implementation details of get_str()
st.with_element(s, |st| do_recursion(n - 1, tail, st))
);
}
// do more stuff with those references (println is just an example)
println!("Item: {}", tmp);
}
fn main() {
do_recursion(100, list /* gotten from somewhere else */, &mut RefStack::new());
}
在上面的示例中,我关心的是如何实现 RefStack
而无需任何每项内存分配。 Vec
的偶尔分配是可以接受的 - 这些分配很少而且相差甚远。 LinkedListNode
只是一个例子 - 实际上它是一些复杂的图形数据结构,但同样适用 - 我只有一个非 mut 引用它,并且给 manager.get_str()
的闭包只提供了一个非 mut str
。请注意,传入闭包的非 mut str
可能仅在 get_str()
实现中构造,因此我们不能假设所有 &str
具有相同的生命周期。
我相当确定如果不将 str
复制到拥有的 String
中,RefStack
无法在安全 Rust 中实现,所以我的问题是这怎么可能在不安全的 Rust 中完成。感觉我也许可以得到这样的解决方案:
- 不安全仅限于
RefStack
的实现
st.get()
返回的引用应该至少与 do_recursion
函数的当前实例一样长(特别是,它应该能够在调用 st.with_element()
,这在逻辑上是安全的,因为 st.get()
返回的 &T
无论如何都不是指 RefStack
拥有的任何内存)
如何在(不安全的)Rust 中实现这样的结构?
我觉得我可以将元素引用转换为指针并将它们存储为指针,但是在将它们转换回引用时,我仍然难以表达上面第二个要点中的要求。或者是否有更好的方法(或者这种结构是否可以在安全的 Rust 中实现,或者已经在某个库中的某个地方)?
免责声明:这个回答原来用的是traits,简直是噩梦; Francis Gagne 正确地指出,使用 Option
作为尾巴是一个更好的选择,因此答案要简单得多。
鉴于您的使用结构,RefStack
中的堆栈跟随堆栈帧的使用,您可以简单地将元素放在堆栈帧上并从中构建堆栈。
这种方法的主要优点是完全安全。您可以查看 whole code here,或按照下面的逐项描述进行操作。
关键是想法是建立一个so-calledcons-list.
#[derive(Debug)]
struct Stack<'a, T> {
element: &'a T,
tail: Option<&'a Stack<'a, T>>,
}
impl<'a, T> Stack<'a, T> {
fn new(element: &'a T) -> Self { Stack { element, tail: None } }
fn top(&self) -> &T { self.element }
fn get(&self, index: usize) -> Option<&T> {
if index == 0 {
Some(self.element)
} else {
self.tail.and_then(|tail| tail.get(index - 1))
}
}
fn tail(&self) -> Option<&'a Stack<'a, T>> { self.tail }
fn push<'b>(&'b self, element: &'b T) -> Stack<'b, T> { Stack { element, tail: Some(self) } }
}
一个简单的用法示例是:
fn immediate() {
let (a, b, c) = (0, 1, 2);
let root = Stack::new(&a);
let middle = root.push(&b);
let top = middle.push(&c);
println!("{:?}", top);
}
它只打印堆栈,产生:
Stack { element: 2, tail: Some(Stack { element: 1, tail: Some(Stack { element: 0, tail: None }) }) }
还有一个更复杂的递归版本:
fn recursive(n: usize) {
fn inner(n: usize, stack: &Stack<'_, i32>) {
if n == 0 {
print!("{:?}", stack);
return;
}
let element = n as i32;
let stacked = stack.push(&element);
inner(n - 1, &stacked);
}
if n == 0 {
println!("()");
return;
}
let element = n as i32;
let root = Stack::new(&element);
inner(n - 1, &root);
}
打印:
Stack { element: 1, tail: Some(Stack { element: 2, tail: Some(Stack { element: 3, tail: None }) }) }
一个缺点是get
性能可能不太好;它具有线性复杂性。另一方面,cache-wise 坚持使用栈帧非常好。如果您主要访问前几个元素,我希望它足够好。
我认为存储原始指针是可行的方法。您需要 PhantomData
来存储生命周期并获得适当的协方差:
use std::marker::PhantomData;
struct RefStack<'a, T: ?Sized> {
content: Vec<*const T>,
_pd: PhantomData<&'a T>,
}
impl<'a, T: ?Sized> RefStack<'a, T> {
fn new() -> Self {
RefStack {
content: Vec::new(),_pd: PhantomData
}
}
fn get(&self, index: usize) -> &'a T {
unsafe { &*self.content[index] }
}
fn len(&self) -> usize {
self.content.len()
}
fn with_element<'t, F: FnOnce(&mut RefStack<'t, T>)>(&mut self, el: &'t T, f: F)
where 'a: 't,
{
self.content.push(el);
let mut tmp = RefStack {
content: std::mem::take(&mut self.content),
_pd: PhantomData,
};
f(&mut tmp);
self.content = tmp.content;
self.content.pop();
}
}
唯一的 unsafe
代码是将指针转换回引用。
棘手的部分是让 with_element
正确。我认为 were 'a: 't
是隐含的,因为整个 impl
都依赖于它(但安全总比遗憾好)。
最后一个问题是如何将RefStack<'a, T>
转换为RefStack<'t, T>
。我很确定我可以 std::transmute
它。但这需要额外注意 unsafe
,并且创建一个新的临时堆栈非常简单。
关于't
生命周期
您可能认为实际上并不需要这个 't
生命周期,但不添加它可能会导致微妙的不合理,因为回调可以调用 get()
并获取生命周期 'a
的值] 实际上比插入的值长。
例如,这段代码不应编译。使用 't
它正确地失败了,但没有它它编译并导致未定义的行为:
fn breaking<'a, 's, 'x>(st: &'s mut RefStack<'a, i32>, v: &'x mut Vec<&'a i32>) {
v.push(st.get(0));
}
fn main() {
let mut st = RefStack::<i32>::new();
let mut y = Vec::new();
{
let i = 42;
st.with_element(&i, |stack| breaking(stack, &mut y));
}
println!("{:?}", y);
}
关于 panic!
。
当做这些不安全的事情时,特别是当你调用用户代码时,就像我们现在在 with_element
中所做的那样,我们必须考虑如果它发生恐慌会发生什么。在 OP 代码中,最后一个对象 不会 被弹出,并且当堆栈展开时,任何 drop
实现都可以看到现在悬空的引用。我的代码在出现恐慌时没问题,因为如果 f(&mut tmp);
悬空引用在本地临时 tmp
中消失,而 self.content
为空。
免责声明:不同的答案;使用不同的 trade-off.
与我的其他答案相比,此答案提供的解决方案是:
- 不安全:它是封装的,但很微妙。
- 使用起来更简单。
- 更简单的代码,可能更快。
想法是 仍然 使用堆栈来绑定引用的生命周期,但将所有生命周期存储在单个 Vec
中用于 O(1) 随机访问.所以我们在栈上构建一个栈,而不是将引用本身存储在栈上。好吗?
完整代码is available here.
堆栈本身很容易定义:
struct StackRoot<T: ?Sized>(Vec<*const T>);
struct Stack<'a, T: ?Sized>{
len: usize,
stack: &'a mut Vec<*const T>,
}
impl<T: ?Sized> StackRoot<T> {
fn new() -> Self { Self(vec!()) }
fn stack(&mut self) -> Stack<'_, T> { Stack { len: 0, stack: &mut self.0 } }
}
Stack
的实现比较棘手,当涉及到 unsafe
时总是如此:
impl<'a, T: ?Sized> Stack<'a, T> {
fn len(&self) -> usize { self.len }
fn get(&self, index: usize) -> Option<&'a T> {
if index < self.len {
// Safety:
// - Index is bounds as per above branch.
// - Lifetime of reference is guaranteed to be at least 'a (see push).
Some(unsafe { &**self.stack.get_unchecked(index) })
} else {
None
}
}
fn push<'b>(&'b mut self, element: &'b T) -> Stack<'b, T>
where
'a: 'b
{
// Stacks could have been built and forgotten, resulting in `self.stack`
// containing references to further elements, so that the newly pushed
// element would not be at index `self.len`, as expected.
//
// Note that on top of being functionally important, it's also a safety
// requirement: `self` should never be able to access elements that are
// not guaranteed to have a lifetime longer than `'a`.
self.stack.truncate(self.len);
self.stack.push(element as *const _);
Stack { len: self.len + 1, stack: &mut *self.stack }
}
}
impl<'a, T: ?Sized> Drop for Stack<'a, T> {
fn drop(&mut self) {
self.stack.truncate(self.len);
}
}
注意这里的unsafe
;不变的是'a
参数总是比压入堆栈的元素的生命周期到目前为止.[=24=更严格]
通过拒绝访问其他成员推送的元素,我们从而保证返回引用的生命周期有效。
它确实需要 do_recursion
的通用定义,但是通用生命周期参数在代码生成时被删除,因此不涉及代码膨胀:
fn do_recursion<'a, 'b>(nodes: &[&'a str], stack: &mut Stack<'b, str>)
where
'a: 'b
{
let tmp: &str = stack.get(stack.len() - 1).expect("Not empty");
println!("{:?}", tmp);
if let [head, tail @ ..] = nodes {
let mut new = stack.push(head);
do_recursion(tail, &mut new);
}
}
一个简单的 main
炫耀:
fn main() {
let nodes = ["Hello", ",", "World", "!"];
let mut root = StackRoot::new();
let mut stack = root.stack();
let mut stack = stack.push(nodes[0]);
do_recursion(&nodes[1..], &mut stack);
}
导致:
"Hello"
","
"World"
"!"
基于,我实现了这个稍微简单一点的版本:
struct RefStack<'a, T: ?Sized + 'static> {
content: Vec<&'a T>,
}
impl<'a, T: ?Sized + 'static> RefStack<'a, T> {
fn new() -> Self {
RefStack {
content: Vec::new(),
}
}
fn get(&self, index: usize) -> &'a T {
self.content[index]
}
fn len(&self) -> usize {
self.content.len()
}
fn with_element<'t, F: >(&mut self, el: &'t T, f: F)
where
F: FnOnce(&mut RefStack<'t, T>),
'a: 't,
{
let mut st = RefStack {
content: std::mem::take(&mut self.content),
};
st.content.push(el);
f(&mut st);
st.content.pop();
self.content = unsafe { std::mem::transmute(st.content) };
}
}
与 rodrigo 解决方案的唯一区别是向量表示为引用向量而不是指针,因此我们不需要 PhantomData
和不安全代码来访问元素。
当一个新元素被压入 with_element()
中的堆栈时,我们要求它的生命周期比具有 a': t'
绑定的现有元素更短。然后我们创建一个生命周期较短的新堆栈,这在安全代码中是可能的,因为我们知道向量中的引用指向的数据甚至生命周期较长 'a
。然后,我们将生命周期为 't
的新元素推送到新向量,再次使用安全代码,只有在我们再次删除该元素后,我们才将向量移回其原始位置。这需要不安全的代码,因为我们这次 将向量中引用的生命周期从 't
延长 到 'a
。我们知道这是安全的,因为向量回到了它的原始状态,但编译器不知道这一点。
我觉得这个版本比罗德里戈几乎相同的版本更能表达意图。 vector 的类型始终是“正确的”,因为它描述了元素实际上是引用,而不是原始指针,并且它总是为 vector 分配正确的生命周期。我们恰好在可能发生不安全事件的地方使用不安全代码——当延长向量中引用的生命周期时。
我正在实现一些递归代码,其中调用堆栈中更深层的函数实例可能需要引用先前帧中的数据。但是,我只能对这些数据进行非 mut 访问,因此我会收到这些数据作为参考。因此,我需要在堆栈数据结构中保留对这些数据的引用,以便可以从更深的实例访问。
举例说明:
// I would like to implement this RefStack class properly, without per-item memory allocations
struct RefStack<T: ?Sized> {
content: Vec<&T>,
}
impl<T: ?Sized> RefStack<T> {
fn new() -> Self { Self{ content: Vec::new() } }
fn get(&self, index: usize) -> &T { self.content[index] }
fn len(&self) -> usize { self.content.len() }
fn with_element<F: FnOnce(&mut Self)>(&mut self, el: &T, f: F) {
self.content.push(el);
f(self);
self.content.pop();
}
}
// This is just an example demonstrating how I would need to use the RefStack class
fn do_recursion(n: usize, node: &LinkedListNode, st: &mut RefStack<str>) {
// get references to one or more items in the stack
// the references should be allowed to live until the end of this function, but shouldn't prevent me from calling with_element() later
let tmp: &str = st.get(rng.gen_range(0, st.len()));
// do stuff with those references (println is just an example)
println!("Item: {}", tmp);
// recurse deeper if necessary
if n > 0 {
let (head, tail): (_, &LinkedListNode) = node.get_parts();
manager.get_str(head, |s: &str| // the actual string is a local variable somewhere in the implementation details of get_str()
st.with_element(s, |st| do_recursion(n - 1, tail, st))
);
}
// do more stuff with those references (println is just an example)
println!("Item: {}", tmp);
}
fn main() {
do_recursion(100, list /* gotten from somewhere else */, &mut RefStack::new());
}
在上面的示例中,我关心的是如何实现 RefStack
而无需任何每项内存分配。 Vec
的偶尔分配是可以接受的 - 这些分配很少而且相差甚远。 LinkedListNode
只是一个例子 - 实际上它是一些复杂的图形数据结构,但同样适用 - 我只有一个非 mut 引用它,并且给 manager.get_str()
的闭包只提供了一个非 mut str
。请注意,传入闭包的非 mut str
可能仅在 get_str()
实现中构造,因此我们不能假设所有 &str
具有相同的生命周期。
我相当确定如果不将 str
复制到拥有的 String
中,RefStack
无法在安全 Rust 中实现,所以我的问题是这怎么可能在不安全的 Rust 中完成。感觉我也许可以得到这样的解决方案:
- 不安全仅限于
RefStack
的实现
st.get()
返回的引用应该至少与do_recursion
函数的当前实例一样长(特别是,它应该能够在调用st.with_element()
,这在逻辑上是安全的,因为st.get()
返回的&T
无论如何都不是指RefStack
拥有的任何内存)
如何在(不安全的)Rust 中实现这样的结构?
我觉得我可以将元素引用转换为指针并将它们存储为指针,但是在将它们转换回引用时,我仍然难以表达上面第二个要点中的要求。或者是否有更好的方法(或者这种结构是否可以在安全的 Rust 中实现,或者已经在某个库中的某个地方)?
免责声明:这个回答原来用的是traits,简直是噩梦; Francis Gagne 正确地指出,使用 Option
作为尾巴是一个更好的选择,因此答案要简单得多。
鉴于您的使用结构,RefStack
中的堆栈跟随堆栈帧的使用,您可以简单地将元素放在堆栈帧上并从中构建堆栈。
这种方法的主要优点是完全安全。您可以查看 whole code here,或按照下面的逐项描述进行操作。
关键是想法是建立一个so-calledcons-list.
#[derive(Debug)]
struct Stack<'a, T> {
element: &'a T,
tail: Option<&'a Stack<'a, T>>,
}
impl<'a, T> Stack<'a, T> {
fn new(element: &'a T) -> Self { Stack { element, tail: None } }
fn top(&self) -> &T { self.element }
fn get(&self, index: usize) -> Option<&T> {
if index == 0 {
Some(self.element)
} else {
self.tail.and_then(|tail| tail.get(index - 1))
}
}
fn tail(&self) -> Option<&'a Stack<'a, T>> { self.tail }
fn push<'b>(&'b self, element: &'b T) -> Stack<'b, T> { Stack { element, tail: Some(self) } }
}
一个简单的用法示例是:
fn immediate() {
let (a, b, c) = (0, 1, 2);
let root = Stack::new(&a);
let middle = root.push(&b);
let top = middle.push(&c);
println!("{:?}", top);
}
它只打印堆栈,产生:
Stack { element: 2, tail: Some(Stack { element: 1, tail: Some(Stack { element: 0, tail: None }) }) }
还有一个更复杂的递归版本:
fn recursive(n: usize) {
fn inner(n: usize, stack: &Stack<'_, i32>) {
if n == 0 {
print!("{:?}", stack);
return;
}
let element = n as i32;
let stacked = stack.push(&element);
inner(n - 1, &stacked);
}
if n == 0 {
println!("()");
return;
}
let element = n as i32;
let root = Stack::new(&element);
inner(n - 1, &root);
}
打印:
Stack { element: 1, tail: Some(Stack { element: 2, tail: Some(Stack { element: 3, tail: None }) }) }
一个缺点是get
性能可能不太好;它具有线性复杂性。另一方面,cache-wise 坚持使用栈帧非常好。如果您主要访问前几个元素,我希望它足够好。
我认为存储原始指针是可行的方法。您需要 PhantomData
来存储生命周期并获得适当的协方差:
use std::marker::PhantomData;
struct RefStack<'a, T: ?Sized> {
content: Vec<*const T>,
_pd: PhantomData<&'a T>,
}
impl<'a, T: ?Sized> RefStack<'a, T> {
fn new() -> Self {
RefStack {
content: Vec::new(),_pd: PhantomData
}
}
fn get(&self, index: usize) -> &'a T {
unsafe { &*self.content[index] }
}
fn len(&self) -> usize {
self.content.len()
}
fn with_element<'t, F: FnOnce(&mut RefStack<'t, T>)>(&mut self, el: &'t T, f: F)
where 'a: 't,
{
self.content.push(el);
let mut tmp = RefStack {
content: std::mem::take(&mut self.content),
_pd: PhantomData,
};
f(&mut tmp);
self.content = tmp.content;
self.content.pop();
}
}
唯一的 unsafe
代码是将指针转换回引用。
棘手的部分是让 with_element
正确。我认为 were 'a: 't
是隐含的,因为整个 impl
都依赖于它(但安全总比遗憾好)。
最后一个问题是如何将RefStack<'a, T>
转换为RefStack<'t, T>
。我很确定我可以 std::transmute
它。但这需要额外注意 unsafe
,并且创建一个新的临时堆栈非常简单。
关于't
生命周期
您可能认为实际上并不需要这个 't
生命周期,但不添加它可能会导致微妙的不合理,因为回调可以调用 get()
并获取生命周期 'a
的值] 实际上比插入的值长。
例如,这段代码不应编译。使用 't
它正确地失败了,但没有它它编译并导致未定义的行为:
fn breaking<'a, 's, 'x>(st: &'s mut RefStack<'a, i32>, v: &'x mut Vec<&'a i32>) {
v.push(st.get(0));
}
fn main() {
let mut st = RefStack::<i32>::new();
let mut y = Vec::new();
{
let i = 42;
st.with_element(&i, |stack| breaking(stack, &mut y));
}
println!("{:?}", y);
}
关于 panic!
。
当做这些不安全的事情时,特别是当你调用用户代码时,就像我们现在在 with_element
中所做的那样,我们必须考虑如果它发生恐慌会发生什么。在 OP 代码中,最后一个对象 不会 被弹出,并且当堆栈展开时,任何 drop
实现都可以看到现在悬空的引用。我的代码在出现恐慌时没问题,因为如果 f(&mut tmp);
悬空引用在本地临时 tmp
中消失,而 self.content
为空。
免责声明:不同的答案;使用不同的 trade-off.
与我的其他答案相比,此答案提供的解决方案是:
- 不安全:它是封装的,但很微妙。
- 使用起来更简单。
- 更简单的代码,可能更快。
想法是 仍然 使用堆栈来绑定引用的生命周期,但将所有生命周期存储在单个 Vec
中用于 O(1) 随机访问.所以我们在栈上构建一个栈,而不是将引用本身存储在栈上。好吗?
完整代码is available here.
堆栈本身很容易定义:
struct StackRoot<T: ?Sized>(Vec<*const T>);
struct Stack<'a, T: ?Sized>{
len: usize,
stack: &'a mut Vec<*const T>,
}
impl<T: ?Sized> StackRoot<T> {
fn new() -> Self { Self(vec!()) }
fn stack(&mut self) -> Stack<'_, T> { Stack { len: 0, stack: &mut self.0 } }
}
Stack
的实现比较棘手,当涉及到 unsafe
时总是如此:
impl<'a, T: ?Sized> Stack<'a, T> {
fn len(&self) -> usize { self.len }
fn get(&self, index: usize) -> Option<&'a T> {
if index < self.len {
// Safety:
// - Index is bounds as per above branch.
// - Lifetime of reference is guaranteed to be at least 'a (see push).
Some(unsafe { &**self.stack.get_unchecked(index) })
} else {
None
}
}
fn push<'b>(&'b mut self, element: &'b T) -> Stack<'b, T>
where
'a: 'b
{
// Stacks could have been built and forgotten, resulting in `self.stack`
// containing references to further elements, so that the newly pushed
// element would not be at index `self.len`, as expected.
//
// Note that on top of being functionally important, it's also a safety
// requirement: `self` should never be able to access elements that are
// not guaranteed to have a lifetime longer than `'a`.
self.stack.truncate(self.len);
self.stack.push(element as *const _);
Stack { len: self.len + 1, stack: &mut *self.stack }
}
}
impl<'a, T: ?Sized> Drop for Stack<'a, T> {
fn drop(&mut self) {
self.stack.truncate(self.len);
}
}
注意这里的unsafe
;不变的是'a
参数总是比压入堆栈的元素的生命周期到目前为止.[=24=更严格]
通过拒绝访问其他成员推送的元素,我们从而保证返回引用的生命周期有效。
它确实需要 do_recursion
的通用定义,但是通用生命周期参数在代码生成时被删除,因此不涉及代码膨胀:
fn do_recursion<'a, 'b>(nodes: &[&'a str], stack: &mut Stack<'b, str>)
where
'a: 'b
{
let tmp: &str = stack.get(stack.len() - 1).expect("Not empty");
println!("{:?}", tmp);
if let [head, tail @ ..] = nodes {
let mut new = stack.push(head);
do_recursion(tail, &mut new);
}
}
一个简单的 main
炫耀:
fn main() {
let nodes = ["Hello", ",", "World", "!"];
let mut root = StackRoot::new();
let mut stack = root.stack();
let mut stack = stack.push(nodes[0]);
do_recursion(&nodes[1..], &mut stack);
}
导致:
"Hello" "," "World" "!"
基于
struct RefStack<'a, T: ?Sized + 'static> {
content: Vec<&'a T>,
}
impl<'a, T: ?Sized + 'static> RefStack<'a, T> {
fn new() -> Self {
RefStack {
content: Vec::new(),
}
}
fn get(&self, index: usize) -> &'a T {
self.content[index]
}
fn len(&self) -> usize {
self.content.len()
}
fn with_element<'t, F: >(&mut self, el: &'t T, f: F)
where
F: FnOnce(&mut RefStack<'t, T>),
'a: 't,
{
let mut st = RefStack {
content: std::mem::take(&mut self.content),
};
st.content.push(el);
f(&mut st);
st.content.pop();
self.content = unsafe { std::mem::transmute(st.content) };
}
}
与 rodrigo 解决方案的唯一区别是向量表示为引用向量而不是指针,因此我们不需要 PhantomData
和不安全代码来访问元素。
当一个新元素被压入 with_element()
中的堆栈时,我们要求它的生命周期比具有 a': t'
绑定的现有元素更短。然后我们创建一个生命周期较短的新堆栈,这在安全代码中是可能的,因为我们知道向量中的引用指向的数据甚至生命周期较长 'a
。然后,我们将生命周期为 't
的新元素推送到新向量,再次使用安全代码,只有在我们再次删除该元素后,我们才将向量移回其原始位置。这需要不安全的代码,因为我们这次 将向量中引用的生命周期从 't
延长 到 'a
。我们知道这是安全的,因为向量回到了它的原始状态,但编译器不知道这一点。
我觉得这个版本比罗德里戈几乎相同的版本更能表达意图。 vector 的类型始终是“正确的”,因为它描述了元素实际上是引用,而不是原始指针,并且它总是为 vector 分配正确的生命周期。我们恰好在可能发生不安全事件的地方使用不安全代码——当延长向量中引用的生命周期时。