Rust中的可变弧

时间:2017-05-23 08:26:52

标签: rust

之前我有一个Sync + Send特征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 个答案:

答案 0 :(得分:3)

请注意,应严格检查所实施的特征方法的合规性:send_message(&mut self, user_id: UserId, text: &str)由于前者对send_message(&self, user_id: UserId, text: &str)的可变引用而不符合self,并且编译器最终会抱怨。< / p>

因此,此处需要内部可变性,因此状态更改可能发生在不可变引用之后。在这种情况下,由于您正在处理其他线程安全的组件,因此您可以考虑使用线程安全的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);
    }
}

答案 1 :(得分:2)

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

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才能拥有Sync TestMessenger。