代码之家  ›  专栏  ›  技术社区  ›  rockerbacon

Rust惯用的方法是什么?

  •  1
  • rockerbacon  · 技术社区  · 2 年前

    我目前正在为Sway WM开发一个小型状态栏。酒吧由多个对象组成,实现 Widget 特质 在所有的小部件中,时钟有点特别。我不仅想渲染它,还想确保它的渲染及时完成,否则显示的时间往往会有点偏离。由于这一要求,我需要将时钟作为 小装置 也作为自己的 Clock .

    我面临的问题是,一旦时钟小部件与其他小部件一起存储在向量中,借用检查器就不允许向量之外的任何可变引用存在。这使得无法将时钟用作的实例 时钟 在主应用程序循环中。

    在不引入运行时开销的情况下,我发现解决这个问题的最干净的方法是通过原始指针和不安全的代码。我想知道这个问题的其他零运行时开销解决方案。

    以下是代码的过度简化版本,说明了当前代码的样子:

    trait Widget {
        fn update_info(&mut self);
        fn render(&self);
    }
    
    struct NetworkStatus{}
    impl Widget for NetworkStatus {
        fn update_info(&mut self) {
            // query the OS for network information
        }
        fn render(&self) {
            println!("Connected wirelessly");
        }
    }
    
    struct Clock{}
    impl Widget for Clock {
        fn update_info(&mut self) {
            // query the OS for the current time
        }
        fn render(&self) {
            println!("2024-03-06T19:25:17");
        }
    }
    impl Clock {
        fn get_alignment_delay(&mut self) -> std::time::Duration {
            return std::time::Duration::new(1, 0);
        }
    }
    
    fn main() {
        let mut netstat = NetworkStatus{};
        let mut clock = Clock{};
        let clock_ptr = &mut clock as *mut Clock;
    
        let mut widgets: Vec<&mut dyn Widget> = vec![
            &mut netstat,
            &mut clock,
        ];
    
        loop {
            for w in widgets.iter_mut() {
                w.update_info();
            }
    
            for w in widgets.iter() {
                w.render();
            }
    
            std::thread::sleep(unsafe {
                (*clock_ptr).get_alignment_delay()
            });
        }
    }
    
    

    我知道不同的体系结构可以解决这个问题,但我只是对学习这个确切问题的一些解决方案感兴趣,这样我下次看到类似的东西时就知道该怎么办了。

    0 回复  |  直到 2 年前
        1
  •  3
  •   Kevin Reid    2 年前

    铁锈不允许 不受控制的 ,共享、可变访问。对于你的应用程序,你需要对时钟进行共享、可变的访问,所以你必须选择一种控制它的方法。

    1. 您可以将每个小部件放在 RefCell , Mutex 或 RwLock 以允许运行时检查变异。

      fn main() {
          let netstat = RefCell::new(NetworkStatus{});
          let clock = RefCell::new(Clock{});
      
          // note these are now `&` references, which may be shared
          let widgets: Vec<&RefCell<dyn Widget>> = vec![
              &netstat,
              &clock,
          ];
      
          loop {
              for w in widgets.iter() {
                  w.borrow_mut().update_info();
              }
      
              for w in widgets.iter() {
                  w.borrow().render();
              }
      
              std::thread::sleep(clock.borrow().get_alignment_delay());
          }
      }
      
    2. 你可以记下时钟在矢量中的位置,然后将其访问为 widgets[index_of_the_clock].get_alignment_delay() 。如果用户可以添加和删除多个动画小部件,并且您想检查所有小部件的睡眠时间,那么这可能是有意义的。

    3. 您可以要求每个小部件都是内部可变的(也就是说,使用 RefCell 对于其自身的所有可变部分),使得 update() 只需要 &self 而不是 &mut self 。然后您可以与共享对小部件的访问权限 & 就像上面的代码示例一样,但不需要包装 RefCell 。(这可能不如第一种方案符合人体工程学。)

    请注意,在所有这些情况下,您可能会发现使用引用向量是有限制的——特别是,如果您想让用户在程序运行时添加新的小部件,那么向量应该 拥有 小部件,而不是借用它们。这意味着矢量的类型为 Vec<Box<dyn Widget>> (或者在情况1中, Vec<Box<RefCell<dyn Widget>>> ). 这并不能阻止您更改小部件,因为 所有权 包括变异的能力。