rust 作为结构成员的选项引用[重复]

wa7juj8i  于 11个月前  发布在  其他
关注(0)|答案(1)|浏览(108)

此问题在此处已有答案

Why can't I store a value and a reference to that value in the same struct?(4个答案)
上个月关门了。
如何摆脱下面的生命周期错误,其中我设置了一个Option引用成员变量来引用本地函数堆栈变量。

use std::collections::HashMap;
use std::sync::mpsc;

struct Mt<'a> {
    list: HashMap<u32, Data>,
    to_be_processed_data_ref: Option<&'a Data>,
    hash_map_data_ref: Option<&'a Data>,
    rx: mpsc::Receiver<Data>,
}

impl <'a> Mt<'a> {
    fn new(rx: mpsc::Receiver<Data>) -> Self {
        Mt {
            list: HashMap::new(),
            to_be_processed_data_ref: None,
            hash_map_data_ref: None,
            rx,
        }
    }
    
    fn add_to_hash_map(&mut self, k: u32, data: Data) {
        self.list.insert(k, data);
    }
    
    fn check(&mut self) {
        let data = self.rx.recv().unwrap();
        
        // make use of self.hash_map_data_ref and self.to_be_processed_data_ref in other Mt member functions and set them to None at the end
        self.to_be_processed_data_ref = Some(&data);
        for (_, val) in &self.list {
            self.hash_map_data_ref = Some(val);
            self.test1();
            self.test2();
            self.test3();
        }
        
        self.hash_map_data_ref = None;
        self.to_be_processed_data_ref = None;
    }
    
    fn test1(&self) {
        // access self.hash_map_data_ref and self.to_be_processed_data_ref
    }
    
    fn test2(&self) {
        // access self.hash_map_data_ref and self.to_be_processed_data_ref
    }
    
    fn test3(&self) {
        // access self.hash_map_data_ref and self.to_be_processed_data_ref
    }
}

struct Data {
    x: f32,
    y: f32,
}

impl Data {
    fn new(x: f32, y: f32) -> Self {
        Data {
            x,
            y,
        }
    }    
}

fn main() {
    // Create a simple streaming channel
    let (tx, rx) = mpsc::channel::<Data>();
    let mut mt = Mt::new(rx);
    
    mt.add_to_hash_map(1, Data::new(10.0f32, 20.0f32));
    mt.add_to_hash_map(2, Data::new(100.0f32, 200.0f32));
    mt.add_to_hash_map(3, Data::new(1000.0f32, 2000.0f32));
    mt.add_to_hash_map(4, Data::new(10000.0f32, 20000.0f32));
    mt.add_to_hash_map(5, Data::new(100000.0f32, 200000.0f32));
    
    tx.send(Data::new(1.0f32, 2.0f32));
    mt.check();
}

字符串

hjzp0vay

hjzp0vay1#

不幸的是,这里存在一个关于生存期的基本问题,最好的办法是重构test1test2test3,以简单地引用datahash_map_data_ref
从表面上看,以下模式:

let data = self.rx.recv().unwrap();
// make use of self.hash_map_data_ref and self.to_be_processed_data_ref in other Mt member functions and set them to None at the end
self.to_be_processed_data_ref = Some(&data);
// call self.testX functions
self.to_be_processed_data_ref = None;

字符串
would seem to be valid -在data被删除或移动之前删除引用。然而,这里有两个问题:

  • 首先,借用检查器无法识别这种类型的模式,并且工作时过于静态,无法识别它。Mt<'a>中的Option<&'a Data>字段要求您在字段中放置的任何引用都必须引用一个与'a一样长的对象,即使您承诺足够快地清除引用,这并不满足关于self'a存活时间与data存活时间的静态检查。
  • 第二,如果任何test函数panic并且panic被捕获,则此代码实际上是不可靠的。通常,捕获的panic可能导致 * 破坏的不变量 *,但这种模式不应导致不可靠。

然而,在这种情况下,panic将允许调用者观察到一个悬空引用:data在panic解除时被丢弃,但引用仍然存在。Here是一个交互式演示,显示了遇到悬空引用时程序中断。为了回答的完整性,我将在底部包含一个代码副本。
确切的结构取决于你需要对列表和Map引用做什么,我不能准确地弄清楚。然而,这里有一个框架应该可以工作:

#[derive(Debug)]
struct Foo {
    rx: std::sync::mpsc::Receiver<String>,
    list: std::collections::HashMap<u32, String>,
}
impl Foo {
    fn add_to_hash_map(&mut self, k: u32, data: String) {
        self.list.insert(k, data);
    }
    fn check(&mut self) {
        // pass the value in instead of calling recv
        // This shouldn't change the behavior much
        let data = self.rx.recv().unwrap();
        for val in self.list.values() {
            self.test1(&data, &val);
            self.test2(&data, &val);
        }
        
    }
    fn test1(&self, data: &String, map_val: &String) {
        if data == map_val {
            println!("foo")
        }
    }
    fn test2(&self, data: &String, map_val: &String) {
        if data.len() == map_val.len() {
            println!("bar")
        }
    }
}

fn main() {

    let (tx, rx) = std::sync::mpsc::channel::<String>();
    let mut foo = Foo {
        rx, list: std::collections::HashMap::new()
    };
    foo.add_to_hash_map(1, "hello".to_string());
    foo.add_to_hash_map(2, "apple".to_string());
    tx.send("apple".to_string());
    foo.check();
}


注意,你不能在test1test2中改变self.list,因为在列表上的迭代已经从它借用了。我想给定test1(&self)的签名,这对你来说应该不是问题。
不健全证明:

use std::panic::AssertUnwindSafe;

#[derive(Debug)]
struct Foo<'a> {
    x: Option<&'a String>
}
impl <'a> Foo<'a> {
    fn set_and_test(&mut self) {
        let s = "hello world".to_string();
        unsafe {
          // Pretend the borrow checker let us do what we wanted to do
          // using the minimum amount of unsafe possible
          self.x = Some(&*(&s as *const String));
        }
        self.action();
        self.x = None;
    }
    fn action(&self) {
        panic!();
    }
}

fn main() {
    let mut foo = Foo {
        x: None
    };
    // Caller calls a panicking function and catches the panic
    let _ = std::panic::catch_unwind(AssertUnwindSafe(|| {
        foo.set_and_test();
    }));
    // Normally this should be sound (but may expose broken invariants)
    // However, in this case, foo.x will be a Some(dangling reference)!
    println!("{:?}", foo)
}

相关问题