为什么这个结构方法仍然借用可变引用?

Why does this struct method still borrow the mutable reference?

提问人:BlueOyster 提问时间:7/13/2023 最后编辑:BlueOyster 更新时间:7/13/2023 访问量:65

问:

我正在尝试实现类似于 Rust 中的 Observer 模式的东西。我对 Rust 有一些不错的经验,但是我无法具体指出以下代码出现此编译问题的原因。任何帮助/解释将不胜感激。

上下文:从本质上讲,我在一个结构体(订阅者)中有一个状态向量,需要将其“连接”到另一个结构体(发布者)。发布者具有对订阅者状态向量的特定索引的可变引用。

我创建了我需要的逻辑类型的非常粗略和简化的版本:

use std::cell::{Cell};


#[derive(Debug)]
pub struct Entity<'a> {
        pub states: Vec<Cell<bool>>,
        pub subscribers: Vec<&'a Cell<bool>>
    }
    
    impl <'a>Entity<'a> {
        pub fn new(size: usize) -> Self {
            Entity {
                states: vec![Cell::new(false); size],
                subscribers: Vec::new()
            }
        }
        
        pub fn connect<'b: 'a>(publisher: &'a mut Entity<'a>, subscriber: &'b mut Entity<'b>) {
            for state in subscriber.states.iter() {
                publisher.subscribers.push(state);
            }
        }
        
        pub fn notify(&mut self) {
            for subscriber in self.subscribers.iter() {
                subscriber.replace(true);
            }
        }
    }


fn main() {
    let mut pub_ = Entity::new(3);
    let mut sub_ = Entity::new(3);
    
    // view initial states of subscriber
    println!("{:?}", sub_);
    
    Entity::connect(&mut pub_, &mut sub_);
    
    // notify the subscriber (change all states)
    pub_.notify();
    
    // view final states of subscriber
    println!("{:?}", sub_);
}

但是,这会产生以下编译器错误:

error[E0499]: cannot borrow `pub_` as mutable more than once at a time
  --> src/main.rs:44:5
   |
41 |     Entity::connect(&mut pub_, &mut sub_);
   |                     --------- first mutable borrow occurs here
...
44 |     pub_.notify();
   |     ^^^^^^^^^^^^^
   |     |
   |     second mutable borrow occurs here
   |     first borrow later used here

error[E0502]: cannot borrow `sub_` as immutable because it is also borrowed as mutable
  --> src/main.rs:47:22
   |
41 |     Entity::connect(&mut pub_, &mut sub_);
   |                                --------- mutable borrow occurs here
...
47 |     println!("{:?}", sub_);
   |                      ^^^^
   |                      |
   |                      immutable borrow occurs here
   |                      mutable borrow later used here
   |

我期待这一点。我的理解是,由于我使用相同的生存期参数,它期望可变引用的生存时间与此完全相同,因此当然,直到 结束它才会被放弃。显然,我还意识到,订阅者的生存时间必须至少与发布者一样长,因为发布者持有对订阅者的引用(并且设置为与这些引用一样长)。'aEntity<'a>'a

我的困惑来自于如果我尝试以下事情:

impl <'a, 'b: 'a, 'c>Entity<'a> {
        pub fn new(size: usize) -> Self {
            Entity {
                states: vec![Cell::new(false); size],
                subscribers: Vec::new()
            }
        }
        
        pub fn connect(publisher: &'c mut Entity<'a>, subscriber: &'c mut Entity<'b>) {
            for state in subscriber.states.iter() {
                publisher.subscribers.push(state);
            }
        }
        
        pub fn notify(&mut self) {
            for subscriber in self.subscribers.iter() {
                subscriber.replace(true);
            }
        }
}
error: lifetime may not live long enough
  --> src/main.rs:22:17
   |
12 | impl <'a, 'b: 'a, 'c>Entity<'a> {
   |       --          -- lifetime `'c` defined here
   |       |
   |       lifetime `'a` defined here
...
22 |                 publisher.subscribers.push(state);
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ argument requires that `'c` must outlive `'a`

在这种情况下,为什么可变引用的生存期必须比 ?为什么可变引用之后没有被删除,我怎样才能做到这一点?EntityEntity::connect(&mut pub_, &mut sub_);

我把它剥离得更厉害,下面的代码完全按照我的想法工作:

use std::cell::{Cell};


pub fn connect<'a, 'b: 'a>(publisher: &mut Vec<&'a Cell<bool>>, subscriber: &'b mut Vec<Cell<bool>>) {
    for state in subscriber.iter() {
        publisher.push(state);
    }
}

pub fn notify<'a>(publisher: &mut Vec<&'a Cell<bool>>) {
    for state in publisher.iter() {
        state.replace(true);
    }
}

fn main() {
    let mut pub_: Vec<&Cell<bool>> = Vec::new();
    let mut sub_ = vec![Cell::new(false); 3];
    
    // inital subscriber states
    println!("{:?}", sub_);
    
    // give the publisher references to the subscriber states
    connect(&mut pub_, &mut sub_);
    
    // make the publisher notify all subscribers (change states)
    notify(&mut pub_);
    
    // final subscriber states
    println!("{:?}", sub_);
}
[Cell { value: false }, Cell { value: false }, Cell { value: false }]
[Cell { value: true }, Cell { value: true }, Cell { value: true }]

其他:

  • 我确实设法让这个版本工作,但它并不像我想要的那么干净。我没有传递两个可变引用,而是传递一个不可变引用(对于订阅者),并允许该方法获得发布者的完全所有权。完成后,我会将发布者归还给发布者。我对这个解决方案不满意。

  • 是的,我知道我可以使用回调函数(我已经有)来实现这一点。我试图避免这种实现方法。还有其他资源展示了 Rust 中的 Observer 模式,它们很有帮助,但并不特别适合我的预期用例。

Rust 所有权

评论


答:

0赞 PitaJ 7/13/2023 #1

下面是一个固定版本:

pub fn connect<'b: 'a>(publisher: &mut Entity<'a>, subscriber: &'a mut Entity<'b>) {
    for state in subscriber.states.iter() {
        publisher.subscribers.push(state);
    }
}

请注意,我从发布者引用中完全删除了显式生存期,并将发布者内部的生存期与对订阅者的引用相匹配。

如果你看到一个,这通常意味着有问题,因为这意味着引用和引用具有相同的生存期,这通常是不可能的,因为这些必须存在才能首先创建。&'a mut Thing<'a>ThingThingThing

评论

0赞 cafce25 7/13/2023
nit:通常不是问题,因为引用在生命周期中是可变的,问题通常只存在于可变引用 () 上,但我想你只是忘记了 ‽&'a Thing<'a>&'a mut Thing<'a>mut
0赞 BlueOyster 7/13/2023
谢谢!固定版本运行良好,您完全消除了我的困惑。你是对的,我不明白可变引用是否/如何(在这种情况下)可以具有与 ;现在我看到它不能。Thing
0赞 Chayim Friedman 7/13/2023
您可以完全摆脱并仅使用 .您还可以使用共享引用而不是可变引用。'b&'a mut Entity<'_>