不安全的Rust中有引用堆栈,但是要确保不安全性不会从堆栈中泄漏出来? 关于't的生存期关于panic!

如何解决不安全的Rust中有引用堆栈,但是要确保不安全性不会从堆栈中泄漏出来? 关于't的生存期关于panic!

我正在实现一些递归代码,其中在调用堆栈中更深的函数实例可能需要引用先前帧中的数据。但是,我只能对这些数据进行非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());
}

在上面的示例中,我担心如何在没有每个项目内存分配的情况下实现RefStackVec偶尔进行的分配是可以接受的-分配很少,而且介于两者之间。 LinkedListNode只是一个示例-实际上,它是一些复杂的图形数据结构,但同样适用-我仅对其具有非mut引用,而给manager.get_str()的闭包仅提供了一个非Mut str。请注意,传递给闭包的非mut str只能在get_str()实现中构造,因此我们不能假定所有&str都具有相同的生存期。

我相当肯定RefStack不能在不将str复制到拥有的String的情况下在安全Rust中实现,所以我的问题是如何在不安全的锈。感觉我也许可以得到这样的解决方案:

  • 不安全仅限于实施RefStack
  • st.get()返回的引用的生存期至少应与do_recursion函数的当前实例一样长(特别是,它应该能够保留对st.with_element()的调用,并且这在逻辑上是安全的,因为&T返回的st.get()并不指向RefStack拥有的任何内存)

如何在(不安全的)Rust中实现这样的结构?

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

解决方法

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

根据使用情况的结构,在使用RefStack中的堆栈之后,使用堆栈框架,您可以简单地将元素放在堆栈框架上,然后从中构建堆栈。

这种方法的主要优点是它是完全安全的。您可以查看whole code here,也可以按照下面的介绍进行介绍。

关键是想法是建立一个所谓的缺点列表。

#[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的性能可能不太好;它具有线性复杂度。另一方面,按高速缓存粘在堆栈帧上非常不错。如果您主要访问前几个元素,那么我希望它足够好。

,

我认为存储原始指针是必经之路。您需要一个PhantomData来存储生存期并获得适当的协方差:

use std::marker::PhantomData;

struct RefStack<'a,T: ?Sized> {
    content: Vec<*const T>,_pd: PhantomData<&'a T>,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();
    }
}

Playground

唯一的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代码中,最后一个对象 not 不会弹出,并且当堆栈展开后,任何drop实现都可以看到悬挂的引用。我的代码可以解决紧急情况,因为如果f(&mut tmp);悬空的引用在tmp为空时死在本地临时self.content中。

,

免责声明:不同的答案;权衡取舍。

与我的其他答案相比,该答案提供了一种解决方案:

  • 不安全:它是封装的,但微妙。
  • 易于使用。
  • 更简单的代码,可能更快。

想法是 still 使用堆栈来绑定引用的生存期,但将所有生存期存储在单个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,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参数始终严格 ,以确保元素到堆栈中的生存期到目前为止

通过拒绝访问其他成员推送的元素,我们保证返回的引用的有效期是有效的。

它确实需要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"
"!"
,

基于rodrigo's answer,我实现了这个稍微简单的版本:

struct RefStack<'a,T: ?Sized + 'static> {
    content: Vec<&'a T>,T: ?Sized + 'static> RefStack<'a,}
    }

    fn get(&self,index: usize) -> &'a T {
        self.content[index]
    }

    fn len(&self) -> usize {
        self.content.len()
    }

    fn with_element<'t,F: >(&mut self,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。我们知道这是安全的,因为向量返回到其原始状态,但是编译器不知道这一点。

我觉得这个版本比Rodrigo几乎相同的版本更好地表达了意图。向量的类型始终是“正确的”,因为它描述了元素实际上是引用,而不是原始指针,并且总是为向量分配正确的生存期。而且,当延长向量中引用的生存期时,我们会在发生潜在不安全情况的地方完全使用不安全代码。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-