I'm trying to write a redis "consumer" which would eventually subscribe to a channel and listen to the messages using PubSub. So far I got this (because under the hood PubSub holds &'a mut Connection)
use redis::{Client, Connection, PubSub, RedisError};
use std::marker::PhantomData;
pub struct Consumer<'a, T> {
con: Connection,
pubsub: PubSub<'a>,
phantom: PhantomData<T>,
}
impl<'a, T> Consumer<'a, T> {
fn new(redis_url: &str) -> Result<Self, RedisError> {
let _client = Client::open(redis_url)?;
let mut con = _client.get_connection()?;
let mut pubsub = con.as_pubsub();
let phantom = PhantomData;
Ok(Self {
con,
pubsub,
phantom,
})
}
}
However this doesn't compile because
error[E0597]: `con` does not live long enough
and
error[E0505]: cannot move out of `con` because it is borrowed
The issue seems to be that my struct has a field holding mutable reference to the other field.
Is there any common workaround for this type of issue?
At the end I would like to have a Consumer struct so that I could write code like this
let consumer = Consumer::new("redis://127.0.0.1");
consumer.subscribe("channel1");
for msg in consumer {
println!("{:?}", msg);
};
where consumer would rely on Pubsub.get_message. Therefore I need to initiate PubSub somehow. Unfortunately, if I understand it correctly, the library doesn't provide "into" method to create "owned" sync PubSub. I think async PubSub version is ok. I would be thankful for any suggestion how to fix it.
I'm not familiar with Redis, but it looks to me like based on the way the type is set up that a connection can't (or shouldn't) be used for other things while a pubsub subscription is active. You could just not store the connection, and instead pass a connection into your Consumer::new method
but I would like have a wrapper such that user doesn't even have to create Connection. Should I create some additional struct or are there any smart pointers which could help me?
That can never work. You can never (even indirectly) create a (useful) self-referential type in safe Rust.* Self-referential types lead to dangling pointers. After all, if such a value were moved, then the self-reference would be invalidated (as it would still point to the previous location of the value).
That could only be the case if the library didn't require the current borrowing scheme. Smart pointers can't keep borrowed data alive through a borrow. Nothing can keep borrowed data alive through a borrow. Borrows don't keep stuff alive – by-value, owning bindings do.
If the library were designed in such a way that one type would accept a reference-counted smart pointer (Rc or Arc) to the other, then using such a smart pointer would help. In this case, it does not.
*: there are some tricks to instantiate self-referential types in safe Rust. They are useless though; eg. one common scheme results in a value that mutably borrows itself forever, so it can't be moved or destroyed.