Recursion 不安全锈蚀的参考堆,但确保不安全性不会从堆中泄漏?

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&

我正在实现一些递归代码,其中调用堆栈中更深层次的函数实例可能需要引用先前帧中的数据。但是,我只有对这些数据的非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,
}
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()
的闭包只提供了一个非mut
str
。请注意,传递到闭包中的非mut
str
只能在
get_str()
实现中构造,因此我们不能假设所有
&str
都具有相同的生存期

我相当确定,如果不将
str
复制到所拥有的
String
s中,
RefStack
就无法在safe-Rust中实现,因此我的问题是如何在safe-Rust中实现这一点。我觉得我可能能够得到这样的解决方案:

  • 不安全性仅限于
    RefStack
  • st.get()
    返回的引用应该至少与
    do\u递归
    函数的当前实例一样长(特别是,它应该能够通过调用
    st.with\u element()
    ,这在逻辑上是安全的,因为
    st.get()返回的
    &T
    并不是指
    RefStack所拥有的任何内存(无论如何
如何在(不安全的)Rust中实现这样的结构

我觉得我可以将元素引用转换为指针,并将它们存储为指针,但在将它们转换回引用时,我仍然会遇到在上面第二个要点中表达需求的困难。或者有没有更好的方法(或者这种结构是否可以在safe Rust中实现,或者已经在某个库中实现)?

免责声明:这个答案最初使用的是traits,这是一场噩梦;弗朗西斯·加涅(Francis Gagne)正确地指出,在尾部使用
选项是一个更好的选择,因此答案大大简化

根据您的使用结构,使用
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) };
    }
}