Recursion 不安全锈蚀的参考堆,但确保不安全性不会从堆中泄漏?
我正在实现一些递归代码,其中调用堆栈中更深层次的函数实例可能需要引用先前帧中的数据。但是,我只有对这些数据的非mut访问权限,因此我将这些数据作为引用接收。因此,我需要在堆栈数据结构中保留对这些数据的引用,可以从更深层次的实例访问这些数据 举例说明:Recursion 不安全锈蚀的参考堆,但确保不安全性不会从堆中泄漏?,recursion,rust,stack,lifetime,unsafe,Recursion,Rust,Stack,Lifetime,Unsafe,我正在实现一些递归代码,其中调用堆栈中更深层次的函数实例可能需要引用先前帧中的数据。但是,我只有对这些数据的非mut访问权限,因此我将这些数据作为引用接收。因此,我需要在堆栈数据结构中保留对这些数据的引用,可以从更深层次的实例访问这些数据 举例说明: // I would like to implement this RefStack class properly, without per-item memory allocations struct RefStack<T: ?Sized&
// 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,
}
impl RefStack{
fn new()->Self{Self{content:Vec::new()}
fn get(&self,index:usize)->&T{self.content[index]}
fn len(&self)->使用{self.content.len()}
带_元素的fn(&mut self,el:&T,f:f){
自我内容推送(el);
f(自我);
self.content.pop();
}
}
//这只是一个演示如何使用RefStack类的示例
fn do_递归(n:usize,node:&LinkedListNode,st:&mut RefStack){
//获取对堆栈中一个或多个项的引用
//应该允许引用一直存在到函数结束,但不应该阻止我稍后使用_element()调用
设tmp:&str=st.get(rng.gen_范围(0,st.len());
//使用这些引用做一些事情(println只是一个示例)
println!(“项目:{}”,tmp);
//如有必要,再深入一步
如果n>0{
let(head,tail):(u,&LinkedListNode)=节点。获取零件();
manager.get_str(head,| s:&str |//实际字符串是get_str()实现细节中的局部变量
带| u元素的st(s,| st | do | u递归(n-1,tail,st))
);
}
//用这些引用做更多的事情(println只是一个例子)
println!(“项目:{}”,tmp);
}
fn main(){
do_递归(100,list/*从其他地方获取*/,&mut RefStack::new());
}
在上面的示例中,我关心的是如何在没有任何每项内存分配的情况下实现RefStack
。Vec
偶尔进行的分配是可以接受的-这是很少的,而且介于两者之间。LinkedListNode
只是一个例子——实际上它是一个复杂的图形数据结构,但同样适用——我只对它有一个非mut引用,而给manager.get_str()
的闭包只提供了一个非mutstr
。请注意,传递到闭包中的非mutstr
只能在get_str()
实现中构造,因此我们不能假设所有&str
都具有相同的生存期
我相当确定,如果不将str
复制到所拥有的String
s中,RefStack
就无法在safe-Rust中实现,因此我的问题是如何在safe-Rust中实现这一点。我觉得我可能能够得到这样的解决方案:
- 不安全性仅限于
RefStack
返回的引用应该至少与st.get()
函数的当前实例一样长(特别是,它应该能够通过调用do\u递归
,这在逻辑上是安全的,因为st.with\u element()
并不是指st.get()返回的
)&T
)RefStack所拥有的任何内存(无论如何
选项是一个更好的选择,因此答案大大简化
根据您的使用结构,使用RefStack
中的堆栈跟踪堆栈框架的使用,您可以简单地将元素放在堆栈框架上,并从中构建堆栈
这种方法的主要优点是完全安全。您可以查看,也可以按照下面的详细说明进行操作
关键是这个想法是建立一个所谓的反对者名单
#[派生(调试)]
结构堆栈,
}
恳求{
fn new(元素:&'at)->自{Stack{element,tail:None}
fn top(&self)->&T{self.element}
fn get(&self,索引:usize)->选项{
如果索引==0{
一些(self.element)
}否则{
self.tail.and|u then(| tail | tail.get(索引-1))
}
}
fn tail(&self)->选项>{self.tail}
fn push{Stack{element,tail:Some(self)}
}
一个简单的用法示例是:
fn立即数(){
设(a,b,c)=(0,1,2);
让root=Stack::new(&a);
让中间=根。推(&b);
让顶部=中间。推动(&c);
println!(“{:?}”,顶部);
}
它只打印堆栈,产生:
还有一个更详细的递归版本:
fn递归(n:usize){
fn inner(n:usize,stack:&stack我认为存储原始指针是一种方法。您需要一个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();
}
}
关于恐慌!
。
当你做这些不安全的事情时,尤其是当你调用用户代码时,正如我们在中使用的“元素< /code >”,我们必须考虑如果它是恐慌的会发生什么。在OP代码中,最后一个对象不会被弹出,当堆栈解开时,任何<代码>下拉>代码>实现可以看到现在悬空的引用。如果出现恐慌,y代码是可以的,因为如果f(&mut tmp);
悬空引用
Stack { element: 1, tail: Some(Stack { element: 2, tail: Some(Stack { element: 3, tail: None }) }) }
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();
}
}
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);
}
"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) };
}
}