可变 Arc 在 Rust 中

3

在此之前,我有一个包含 Sync + Send 特性的 trait,名为 SyncMessenger:

trait Messenger {
    fn send_message(&self, user_id: UserId, text: &str);
}

trait SyncMessenger: Messenger + Sync + Send {}

它的实现:

pub struct DiscordMessenger {
    discord: Arc<Discord>, // (Discord is Sync and Send already)
}
impl Messenger for DiscordMessenger {
    fn send_message(&self, user_id: UserId, text: &str) {
        self.discord.send_message(user_id, text, false);
    }
}
impl SyncMessenger for DiscordMessenger {}

并且使用它:

struct Bot {
    messenger: Arc<SyncMessenger>,
}
impl Bot {
    pub fn new() -> Bot {
        Bot { messenger: Arc::new(DiscordMessenger::new()) }
    }   

    fn messenger(&self) -> Arc<SyncMessenger> {
        self.messenger.clone()
    }
}

struct PingCommand {
    fn fire(&mut self, bot: &mut Bot) {
        bot.messenger().send_message(UserId(0), "Pong");
    }
}

一切正常。现在我想实现TestMessenger,它不会通过网络发送消息,而是在Self中切换标志:

#[cfg(test)]
struct TestMessenger {
    pub message_sent: bool,
}
impl Messenger for TestMessenger {
    fn send_message(&mut self, user_id: UserId, text: &str) { // we have `&mut self` here
        self.message_sent = true;
    }
}

所以我需要在特征和实现中的每个地方将send_message(&self)更改为send_message(&mut self)。我已经这样做了,但之后无法编译我的用户代码:

struct PingCommand {
    fn fire(&mut self, bot: &mut Bot) {
        bot.messenger().send_message(UserId(0), "Pong");
    }
}

错误提示:
   |
12 |         let _ = bot.messenger().send_message(UserId(0),
   |                 ^^^^^^^^^^^^^^^ cannot borrow as mutable

error: aborting due to previous error

我找到了一种方法,虽然它能够解决问题,但我觉得它非常丑陋(而且使用了unwrap(),我希望避免这种情况):
let _ = Arc::get_mut(&mut bot.messenger()).unwrap().send_message(UserId(0),

因此,问题在于如何尽可能简单地完成此操作,而不需要使用 unwrap() 或像 Arc::get_mut 这样的静态方法?为什么简单的 fn messenger(&self) -> Arc<SyncMessenger> 无法调用 mut 方法?

2个回答

7

您可以使用内部可变性来更改不可变引用背后的数据。

use std::cell::Cell;
struct TestMessenger {
    pub message_sent: Cell<bool>,
}
impl Messenger for TestMessenger {
    fn send_message(&self, user_id: UserId, text: &str) { 
        self.message_sent.set(true);
    }
}

这个结构体适用于单线程情况。你需要使用std::sync::Mutex代替Cell,以便为TestMessenger实现Sync


5
请注意,实现的trait方法应严格检查是否符合要求:send_message(&mut self, user_id: UserId, text: &str)不符合send_message(&self, user_id: UserId, text: &str),因为前者对self有可变引用,编译器最终会报错。
因此,在这里需要内部可变性,以便在不可变引用后面发生状态更改。在这种情况下,由于您正在处理其他线程安全组件,因此可以考虑使用线程安全的AtomicBool
use std::sync::atomic::AtomicBool;

#[cfg(test)]
struct TestMessenger {
    pub message_sent: AtomicBool,
}
impl Messenger for TestMessenger {
    fn send_message(&self, user_id: UserId, text: &str) { // we have `&mut self` here
        self.message_sent.store(true, Ordering::AcqRel);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接