How to make a subscriber object with RAII properties? - rust

I'm talking to some hardware over a link with kind of a publisher/subscriber model. In C++, I did subscriptions with RAII to remember to always unsubscribe, but I can't seem to get the ownerships/borrows right in rust.
Naively, this is something like what I would like to do. send and receive probably needs to be &mut self, so as I understand, Subscription needs mutable access to the Transport.
struct Transport;
impl Transport {
pub fn send(&mut self, cmd: &str) { unimplemented!() }
pub fn subscribe(&mut self, cmd: &str) -> Subscription {
self.send("subscribe-with-params");
Subscription { trans: &mut self }
}
}
struct Subscription {
trans: &mut Transport,
}
impl Drop for Subscription {
fn drop(&mut self) {
self.trans.send("unsubscribe-with params");
}
}
impl Subscription {
fn receive(&mut self) -> &[u8] { /*blocking wait for data*/ }
}
fn test(t: Transport) {
// Need to subscribe before command, as command might generate status messages
let mut status_sub = t.subscribe("status-message");
{
let mut short_lived_sub = t.subscribe("command_reply");
t.send("command");
short_lived_sub.receive(); // Wait for ack
}
loop {
println!("{:?}", status_sub.receive());
/*processing of status */
}
}
There are at least two problems here. One is how Subscription should keep some reference to it's "parent", the Transport, and another is the problem in fn test that I can't borrow Transport twice for two different subscriptions.
I have a feeling that I'm kind of asking the wrong question here, so maybe there's a good way of approaching this in a different way entirely?

It is problematic for your Subscription to hold a mutable reference to Transport because, as you discovered, you'll only be able to hold one at a time and you won't be able to do anything else with the transport in the meantime.
Instead, you can use an Rc (for shared ownership) and RefCell (for interior mutability):
use std::rc::Rc;
use std::cell::RefCell;
struct TransportInner;
pub struct Transport {
inner: Rc<RefCell<TransportInner>>,
}
pub struct Subscription {
trans: Rc<RefCell<TransportInner>>
}
impl TransportInner {
pub fn send(&mut self, cmd: &str) { }
}
impl Transport {
pub fn send(&mut self, cmd: &str) {
self.inner.borrow_mut().send(cmd)
}
pub fn subscribe(&mut self, cmd: &str) -> Subscription {
self.send("subscribe-with-params");
Subscription { trans: Rc::clone(&self.inner) }
}
}
impl Drop for Subscription {
fn drop(&mut self) {
self.trans.borrow_mut().send("unsubscribe-with params");
}
}
You can do this without splitting it into an inner and outer structure, but that would require the user to access the Transport via an Rc too, which could be unwieldy.
If you need this to work across threads, you should use Arc<Mutex> instead.

Related

How to store a callback in a struct that can change the struct's internal state?

In one of my projects, I would like to store a function pointer used as a callback to change the state of a struct. I've tried different things, but always encountered errors.
Consider the following situation (playground):
struct CallbackStruct {
callback: fn(i32) -> i32,
}
struct MainStruct {
pub callback_struct: Vec<CallbackStruct>,
pub intern_state: i32,
}
impl MainStruct {
pub fn new() -> MainStruct {
let result = MainStruct {
callback_struct: Vec::new(),
intern_state: 0,
};
// push a new call back struct
result.callback_struct.push(CallbackStruct{callback: result.do_stuff});
return result;
}
pub fn do_stuff(&mut self, i: i32) -> i32 {
self.intern_state = i * 2 + 1;
self.intern_state
}
}
fn main() {
let my_struct = MainStruct::new();
}
Here, I'm trying to keep a callback to the MainStruct, that can change it's internal state. This callback will only be stored by other structs owned by this main structure, so I don't think I'm having lifetimes issues - as long as the callback exists, the main struct does as well as it kind of own it.
Now, I'm wondering if having such a callback (with the &mut self reference) isn't a borrow of the main struct, preventing me from having multiple of them, or even keeping them?
In the example, I'm keeping a Vec of the CallbackStruct because I may have different structs all having these kinds of callbacks.
In c/c++, I can go with functions pointers, and I couldn't find a way to store such things in Rust.
How would one implement such a thing?
The problem that Rust is preventing is the possibility that being able to mutate the struct that holds the callback allows you to mutate or destroy the callback while it is executing. That is a problem.
If it is a common pattern that you want your callbacks to modify the structure that invokes the callback, then there's a couple options. Both require adjusting the function signature to pass-in data they are allowed to mutate. This would also allow multiple callbacks to mutate the same state since they only hold the mutable reference while they are running.
#1: Keep the state and callbacks separate
The idea is that the callback is explicitly given mutable access to the state which does not include the callback itself. This can be done by constructing a separate structure to hold the non-callback data, as shown here, or you can simply pass in multiple parameters:
struct InternalState(i32);
impl InternalState {
fn do_stuff(&mut self, i: i32) {
self.0 = i * 2 + 1;
}
}
struct MainStruct {
callbacks: Vec<Box<dyn FnMut(&mut InternalState)>>,
state: InternalState,
}
impl MainStruct {
fn new() -> MainStruct {
MainStruct {
callbacks: Vec::new(),
state: InternalState(0),
}
}
fn push(&mut self, f: Box<dyn FnMut(&mut InternalState)>) {
self.callbacks.push(f);
}
fn invoke(&mut self) {
for callback in &mut self.callbacks {
callback(&mut self.state)
}
}
}
fn main() {
let mut my_struct = MainStruct::new();
my_struct.push(Box::new(|state| { state.do_stuff(1); }));
my_struct.push(Box::new(|state| { state.do_stuff(2); }));
my_struct.invoke();
dbg!(my_struct.state.0);
}
#2 Remove the callback from the struct before executing it
You can mutate the whole struct itself if you remove the callback being ran. This can be done via the take-and-replace method used in this question. This has the added benefit that you have the opportunity to add new callbacks; you just have to reconcile the two sets of callbacks when putting them back:
struct InternalState(i32);
impl InternalState {
fn do_stuff(&mut self, i: i32) {
self.0 = i * 2 + 1;
}
}
struct MainStruct {
callbacks: Vec<Box<dyn FnMut(&mut MainStruct)>>,
state: InternalState,
}
impl MainStruct {
fn new() -> MainStruct {
MainStruct {
callbacks: Vec::new(),
state: InternalState(0),
}
}
fn push(&mut self, f: Box<dyn FnMut(&mut MainStruct)>) {
self.callbacks.push(f);
}
fn invoke(&mut self) {
let mut callbacks = std::mem::take(&mut self.callbacks);
for callback in &mut callbacks {
callback(self)
}
self.callbacks = callbacks;
}
}
fn main() {
let mut my_struct = MainStruct::new();
my_struct.push(Box::new(|main| { main.state.do_stuff(1); }));
my_struct.push(Box::new(|main| { main.state.do_stuff(2); }));
my_struct.invoke();
dbg!(my_struct.state.0);
}
You'll also noticed I changed the code from function pointers fn(i32) -> i32 to function trait objects Box<dyn FnMut(i32) -> i32> since the latter is much more flexible and common since it can actually capture other variables if needed.

Trait with default implementation and required struct member

I have a rust trait which is supposed to add a value to vector. In order for the add_job function to be working, it must be made sure that the vector exists when the trait is implemented for a concrete struct.
The following code fails of course, because jobs is never implemented. It's just there to demonstrate my intention:
trait Person {
// default implementation of add job
fn add_job(&self, job: String) {
self.jobs.push(job)
}
}
struct Customer {
// add_job is used as default implementation
// provided by trait
}
impl Person for Customer {
// some stuff
}
fn main() {
let mut george = Customer {};
george.add_job("programmer".to_string());
}
Is there a way to have a trait which also provides struct members?
Propably not, but what would be the "rustful" way to solve the above problem?
Traits can't provide or require struct fields. Though there is an RFC (#1546) about allowing fields in traits. However, there isn't any unstable features allowing this (yet?).
You can still simplify what you're trying to do though. I've taken the liberty to rename and change your trait, to be able to provide more thorough examples.
Let's consider that we have a Jobs trait. Which defines various methods, that all requires the jobs: Vec<String> field.
trait Jobs {
fn add_job(&mut self, job: String);
fn clear_jobs(&mut self);
fn count_jobs(&self) -> usize;
}
Using a macro
One solution could be to use a macro, which implements all those methods.
macro_rules! impl_jobs_with_field {
($($t:ty),+ $(,)?) => ($(
impl Jobs for $t {
fn add_job(&mut self, job: String) {
self.jobs.push(job);
}
fn clear_jobs(&mut self) {
self.jobs.clear();
}
fn count_jobs(&self) -> usize {
self.jobs.len()
}
}
)+)
}
Then you can easily reuse the code, by using the macro.
struct Person {
jobs: Vec<String>,
}
struct Customer {
jobs: Vec<String>,
}
impl_jobs_with_field!(Person);
impl_jobs_with_field!(Customer);
// or
impl_jobs_with_field!(Person, Customer);
Using a second HasJobs trait
Another solution could be to introduce a second HasJobs trait. Then you can use a blanket implementation for Jobs if a type implements HasJobs.
trait HasJobs {
fn jobs(&self) -> &[String];
fn jobs_mut(&mut self) -> &mut Vec<String>;
}
impl<T: HasJobs> Jobs for T {
fn add_job(&mut self, job: String) {
self.jobs_mut().push(job);
}
fn clear_jobs(&mut self) {
self.jobs_mut().clear();
}
fn count_jobs(&self) -> usize {
self.jobs().len()
}
}
Now HasJobs still needs to be implemented for all your types. But if Jobs has a significant amount of methods. Then implementing HasJobs is a lot easier to deal with. Which we can also do using a macro:
macro_rules! impl_has_jobs {
($($t:ty),+ $(,)?) => ($(
impl HasJobs for $t {
fn jobs(&self) -> &[String] {
&self.jobs
}
fn jobs_mut(&mut self) -> &mut Vec<String> {
&mut self.jobs
}
}
)+)
}
Then once again, you just do:
struct Person {
jobs: Vec<String>,
}
struct Customer {
jobs: Vec<String>,
}
impl_has_jobs!(Person);
impl_has_jobs!(Customer);
// or
impl_has_jobs!(Person, Customer);
Using Deref and DerefMut
Lastly, if Customer is always a Person, then you could change Person into a struct and use composition, i.e. add person: Person to Customer (and other types).
So first, impl Jobs for Person:
struct Person {
jobs: Vec<String>,
}
impl Jobs for Person {
fn add_job(&mut self, job: String) {
self.jobs.push(job);
}
fn clear_jobs(&mut self) {
self.jobs.clear();
}
fn count_jobs(&self) -> usize {
self.jobs.len()
}
}
Then now you can use Deref and DerefMut to dereference Customer to Person. Thus all Person's methods are available directly through Customer.
However, this solution only works if you only have one "trait", i.e. you can only Deref Customer to Person. You wouldn't be able to also Deref Customer to SomethingElse.
use std::ops::{Deref, DerefMut};
struct Customer {
person: Person,
}
impl Deref for Customer {
type Target = Person;
fn deref(&self) -> &Self::Target {
&self.person
}
}
impl DerefMut for Customer {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.person
}
}

Change object type between implemented traits [duplicate]

I have the following sketch of an implementation:
trait Listener {
fn some_action(&mut self);
fn commit(self);
}
struct FooListener {}
impl Listener for FooListener {
fn some_action(&mut self) {
println!("{:?}", "Action!!");
}
fn commit(self) {
println!("{:?}", "Commit");
}
}
struct Transaction {
listeners: Vec<Box<dyn Listener>>,
}
impl Transaction {
fn commit(self) {
// How would I consume the listeners and call commit() on each of them?
}
}
fn listener() {
let transaction = Transaction {
listeners: vec![Box::new(FooListener {})],
};
transaction.commit();
}
I can have Transactions with listeners on them that will call the listener when something happens on that transaction. Since Listener is a trait, I store a Vec<Box<Listener>>.
I'm having a hard time implementing commit for Transaction. Somehow I have to consume the boxes by calling commit on each of the stored Listeners, but I can't move stuff out of a box as far as I know.
How would I consume my listeners on commit?
Applying commit to the boxed object is not allowed because the trait object doesn't know its size (and it's not constant at compile-time). Since you plan to use listeners as boxed objects, what you can do is acknowledge that commit will be invoked on the box and change its signature accordingly:
trait Listener {
fn some_action(&mut self);
fn commit(self: Box<Self>);
}
struct FooListener {}
impl Listener for FooListener {
fn some_action(&mut self) {
println!("{:?}", "Action!!");
}
fn commit(self: Box<Self>) {
println!("{:?}", "Commit");
}
}
This enables Transaction to compile as you wrote it, because inside the implementation of FooListener the size of Self is well known and it is perfectly possible to move the object out of the box and consume both.
The price of this solution is that Listener::commit now requires a Box. If that is not acceptable, you could declare both commit(self) and commit_boxed(self: Box<Self>) in the trait, requiring all types to implement both, possibly using private functions or macros to avoid code duplication. This is not very elegant, but it would satisfy both the boxed and unboxed use case without loss of performance.
With the unsized_locals feature enabled, the natural code works as-is:
// 1.37.0-nightly 2019-06-03 6ffb8f53ee1cb0903f9d
#![feature(unsized_locals)]
// ...
impl Transaction {
fn commit(self) {
for l in self.listeners {
l.commit()
}
}
}
The accepted answer shows what to do when you have the agency to modify the original Listener trait. If you don't have that option, i.e. if you control the Transaction type, but not Listener and its implementations, read on.
First we create a helper trait that is object-safe because none of its methods consume self:
trait DynListener {
fn some_action(&mut self);
fn commit(&mut self);
}
To use this trait everywhere Listener is usable, we will provide a blanket implementation of the trait. Normally such an implementation would implement DynListener for all types T: Listener. But that doesn't work here because Listener::commit() requires consuming self, and DynListener::commit() only receives a reference, so calling Listener::commit() would fail to compile with "cannot move out of borrowed content". To work around that, we implement DynListener for Option<T> instead. This allows us to use Option::take() to get an owned value to pass to Listener::commit():
impl<T: Listener> DynListener for Option<T> {
fn some_action(&mut self) {
// self is &mut Option<T>, self.as_mut().unwrap() is &mut T
self.as_mut().unwrap().some_action();
}
fn commit(&mut self) {
// self is &mut Option<T>, self.take().unwrap() is T
self.take().unwrap().commit();
}
}
DynListener::commit() takes value out of the Option, calls Listener::commit() on the value, and leaves the option as None. This compiles because the value is not "unsized" in the blanket implementation where the size of each individual T is known. The downside is that we are allowed to call DynListener::commit() multiple times on the same option, with all attempts but the first panicking at run time.
The remaining work is to modify Transaction to make use of this:
struct Transaction {
listeners: Vec<Box<dyn DynListener>>,
}
impl Transaction {
fn commit(self) {
for mut listener in self.listeners {
listener.commit();
}
}
}
fn listener() {
let transaction = Transaction {
listeners: vec![Box::new(Some(FooListener {}))],
};
transaction.commit();
}
Playground
According to user4815162342's answer, I wrote a macro to generate commit_boxed(self: Box<Self>) here.
Furthermore, after adding self: Box<Self> signature, you can easily implement trait Listener for Box<dyn Listener>. That means you can call box.commit() just as listener.commit().
impl Listener for Box<dyn Listener> {
fn some_action(&mut self) {
//...
}
fn commit(self) {
Listener::commit_boxed(self)
}
fn commit_boxed(self:Box<Self>) {
Listener::commit_boxed(*self)
}
}

How to call a method that consumes self on a boxed trait object?

I have the following sketch of an implementation:
trait Listener {
fn some_action(&mut self);
fn commit(self);
}
struct FooListener {}
impl Listener for FooListener {
fn some_action(&mut self) {
println!("{:?}", "Action!!");
}
fn commit(self) {
println!("{:?}", "Commit");
}
}
struct Transaction {
listeners: Vec<Box<dyn Listener>>,
}
impl Transaction {
fn commit(self) {
// How would I consume the listeners and call commit() on each of them?
}
}
fn listener() {
let transaction = Transaction {
listeners: vec![Box::new(FooListener {})],
};
transaction.commit();
}
I can have Transactions with listeners on them that will call the listener when something happens on that transaction. Since Listener is a trait, I store a Vec<Box<Listener>>.
I'm having a hard time implementing commit for Transaction. Somehow I have to consume the boxes by calling commit on each of the stored Listeners, but I can't move stuff out of a box as far as I know.
How would I consume my listeners on commit?
Applying commit to the boxed object is not allowed because the trait object doesn't know its size (and it's not constant at compile-time). Since you plan to use listeners as boxed objects, what you can do is acknowledge that commit will be invoked on the box and change its signature accordingly:
trait Listener {
fn some_action(&mut self);
fn commit(self: Box<Self>);
}
struct FooListener {}
impl Listener for FooListener {
fn some_action(&mut self) {
println!("{:?}", "Action!!");
}
fn commit(self: Box<Self>) {
println!("{:?}", "Commit");
}
}
This enables Transaction to compile as you wrote it, because inside the implementation of FooListener the size of Self is well known and it is perfectly possible to move the object out of the box and consume both.
The price of this solution is that Listener::commit now requires a Box. If that is not acceptable, you could declare both commit(self) and commit_boxed(self: Box<Self>) in the trait, requiring all types to implement both, possibly using private functions or macros to avoid code duplication. This is not very elegant, but it would satisfy both the boxed and unboxed use case without loss of performance.
With the unsized_locals feature enabled, the natural code works as-is:
// 1.37.0-nightly 2019-06-03 6ffb8f53ee1cb0903f9d
#![feature(unsized_locals)]
// ...
impl Transaction {
fn commit(self) {
for l in self.listeners {
l.commit()
}
}
}
The accepted answer shows what to do when you have the agency to modify the original Listener trait. If you don't have that option, i.e. if you control the Transaction type, but not Listener and its implementations, read on.
First we create a helper trait that is object-safe because none of its methods consume self:
trait DynListener {
fn some_action(&mut self);
fn commit(&mut self);
}
To use this trait everywhere Listener is usable, we will provide a blanket implementation of the trait. Normally such an implementation would implement DynListener for all types T: Listener. But that doesn't work here because Listener::commit() requires consuming self, and DynListener::commit() only receives a reference, so calling Listener::commit() would fail to compile with "cannot move out of borrowed content". To work around that, we implement DynListener for Option<T> instead. This allows us to use Option::take() to get an owned value to pass to Listener::commit():
impl<T: Listener> DynListener for Option<T> {
fn some_action(&mut self) {
// self is &mut Option<T>, self.as_mut().unwrap() is &mut T
self.as_mut().unwrap().some_action();
}
fn commit(&mut self) {
// self is &mut Option<T>, self.take().unwrap() is T
self.take().unwrap().commit();
}
}
DynListener::commit() takes value out of the Option, calls Listener::commit() on the value, and leaves the option as None. This compiles because the value is not "unsized" in the blanket implementation where the size of each individual T is known. The downside is that we are allowed to call DynListener::commit() multiple times on the same option, with all attempts but the first panicking at run time.
The remaining work is to modify Transaction to make use of this:
struct Transaction {
listeners: Vec<Box<dyn DynListener>>,
}
impl Transaction {
fn commit(self) {
for mut listener in self.listeners {
listener.commit();
}
}
}
fn listener() {
let transaction = Transaction {
listeners: vec![Box::new(Some(FooListener {}))],
};
transaction.commit();
}
Playground
According to user4815162342's answer, I wrote a macro to generate commit_boxed(self: Box<Self>) here.
Furthermore, after adding self: Box<Self> signature, you can easily implement trait Listener for Box<dyn Listener>. That means you can call box.commit() just as listener.commit().
impl Listener for Box<dyn Listener> {
fn some_action(&mut self) {
//...
}
fn commit(self) {
Listener::commit_boxed(self)
}
fn commit_boxed(self:Box<Self>) {
Listener::commit_boxed(*self)
}
}

Mutable Arc in Rust

Earlier I had a Sync + Send trait SyncMessenger:
trait Messenger {
fn send_message(&self, user_id: UserId, text: &str);
}
trait SyncMessenger: Messenger + Sync + Send {}
It's implementation:
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 {}
And using it:
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");
}
}
Everything worked fine. Now I want to implement TestMessenger which does not really send a message through a network but toggles a flag in Self instead:
#[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;
}
}
So I need to change send_message(&self) to send_message(&mut self) everywhere (in traits and in implementations). I did that but after I can't compile my user code:
struct PingCommand {
fn fire(&mut self, bot: &mut Bot) {
bot.messenger().send_message(UserId(0), "Pong");
}
}
Gives error:
|
12 | let _ = bot.messenger().send_message(UserId(0),
| ^^^^^^^^^^^^^^^ cannot borrow as mutable
error: aborting due to previous error
I have found something that works but it looks very ugly to me (and requires unwrap() which I would like to avoid):
let _ = Arc::get_mut(&mut bot.messenger()).unwrap().send_message(UserId(0),
So the question here is how to do that as much simple as possible, without unwrap()s, static methods like Arc::get_mut? Why simple fn messenger(&self) -> Arc<SyncMessenger> is not possible to call mut methods?
You can use interior mutability to change data behind immutable references.
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);
}
}
This struct is for single-treaded case. You'll need std::sync::Mutex instead of Cell to have Sync for TestMessenger.
Note that trait methods implemented should be strictly checked for compliance: send_message(&mut self, user_id: UserId, text: &str) is not compliant with send_message(&self, user_id: UserId, text: &str) due to the former's mutable reference to self, and the compiler would eventually complain.
Therefore, interior mutability is required here, so that state changes may happen behind an immutable reference. In this case, since you're dealing with other thread-safe components, you may consider using the thread-safe 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);
}
}

Resources