Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

a struct with channel and queue #25

Open
kaxap opened this issue Jun 28, 2020 · 4 comments
Open

a struct with channel and queue #25

kaxap opened this issue Jun 28, 2020 · 4 comments

Comments

@kaxap
Copy link

kaxap commented Jun 28, 2020

I wanted to create a constructor which setups and returns the following struct:

pub struct SomeConsumer<'a> {
    conn: amiquip::Connection,
    chan: amiquip::Channel,
    queue: Option<amiquip::Queue<'a>>,
    consumer: Option<amiquip::Consumer<'a>>,
}

But since queue has reference to its Channel (which is the same as chan field), I believe it is not possible to create such struct?
Lib's queue struct:

/// Handle for a declared AMQP queue.
pub struct Queue<'a> {
    channel: &'a Channel,
    name: String,
    message_count: Option<u32>,
    consumer_count: Option<u32>,
}

constructor:

pub struct SomeConsumer<'a> {
    conn: amiquip::Connection,
    chan: amiquip::Channel,
    queue: Option<amiquip::Queue<'a>>,
    consumer: Option<amiquip::Consumer<'a>>,
}

impl<'a> SomeConsumer<'a> {
    pub fn new(properties: &'a Properties) -> Result<Self, AppErr> {

        let mut conn: Connection = amiquip::Connection::insecure_open(&properties.rabbitmq.uri)
            .context("Could not open connection to RabbitMQ")?;

        let chan: Channel = conn.open_channel(None)
            .context("Could not open channel")?;

        let q = chan.queue_declare_passive(&properties.rabbitmq.queue_name)
            .context(format!("Could not passively declare queue {}", &properties.rabbitmq.queue_name))?;

        let consumer = q.consume(amiquip::ConsumerOptions {
            no_local: false,
            no_ack: false,
            exclusive: false,
            arguments: Default::default(),
        }).context("Could not start consumer")?;

        let res = SomeConsumer {
            queue: Some(q),
            conn,
            chan,
            consumer: Some(consumer),
        };

        Ok(res)
    }
}

This results in error:

error[E0505]: cannot move out of `chan` because it is borrowed
  --> src\consumer.rs:55:13
   |
33 | impl<'a> SomeConsumer<'a> {
   |      -- lifetime `'a` defined here
...
42 |         let q = chan.queue_declare_passive(&properties.rabbitmq.queue_name)
   |                 ---- borrow of `chan` occurs here
...
55 |             chan,
   |             ^^^^ move out of `chan` occurs here
...
59 |         Ok(res)
   |         ------- returning this value requires that `chan` is borrowed for `'a`

error[E0515]: cannot return value referencing local variable `chan`
  --> src\consumer.rs:59:9
   |
42 |         let q = chan.queue_declare_passive(&properties.rabbitmq.queue_name)
   |                 ---- `chan` is borrowed here
...
59 |         Ok(res)
   |         ^^^^^^^ returns a value referencing data owned by the current function

So I tried to create a separate method for queue declaration so it would have a reference to channel after it has been moved:

pub struct SomeConsumer<'a> {
    conn: amiquip::Connection,
    chan: amiquip::Channel,
    queue: Option<amiquip::Queue<'a>>,
    consumer: Option<amiquip::Consumer<'a>>,
}

impl<'a> SomeConsumer<'a> {
    pub fn new(properties: &'a Properties) -> Result<Self, AppErr> {

        let mut conn: Connection = amiquip::Connection::insecure_open(&properties.rabbitmq.uri)
            .context("Could not open connection to RabbitMQ")?;

        let chan: Channel = conn.open_channel(None)
            .context("Could not open channel")?;

        let res = SomeConsumer {
            queue: None,
            conn,
            chan,
            consumer: None,
        };

        Ok(res)
    }

    pub fn declare(&mut self, properties: &'a Properties) -> Result<(), AppErr> {
        let q = self.chan.queue_declare_passive(&properties.rabbitmq.queue_name)
            .context(format!("Could not passively declare queue {}", &properties.rabbitmq.queue_name))?;

        let consumer = q.consume(amiquip::ConsumerOptions {
            no_local: false,
            no_ack: false,
            exclusive: false,
            arguments: Default::default(),
        }).context("Could not start consumer")?;

        self.queue = Some(q);
        self.consumer = Some(consumer);

        Ok(())
    }
}

And that gives another error:

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
  --> src\consumer.rs:53:27
   |
53 |         let q = self.chan.queue_declare_passive(&properties.rabbitmq.queue_name)
   |                           ^^^^^^^^^^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 52:5...
  --> src\consumer.rs:52:5
   |
52 | /     pub fn declare(&mut self, properties: &'a Properties) -> Result<(), AppErr> {
53 | |         let q = self.chan.queue_declare_passive(&properties.rabbitmq.queue_name)
54 | |             .context(format!("Could not passively declare queue {}", &properties.rabbitmq.queue_name))?;
55 | |
...  |
66 | |         Ok(())
67 | |     }
   | |_____^
note: ...so that reference does not outlive borrowed content
  --> src\consumer.rs:53:17
   |
53 |         let q = self.chan.queue_declare_passive(&properties.rabbitmq.queue_name)
   |                 ^^^^^^^^^
note: but, the lifetime must be valid for the lifetime `'a` as defined on the impl at 33:6...
  --> src\consumer.rs:33:6
   |
33 | impl<'a> SomeConsumer<'a> {
   |      ^^
note: ...so that the expression is assignable
  --> src\consumer.rs:64:25
   |
64 |         self.consumer = Some(consumer);
   |                         ^^^^^^^^^^^^^^
   = note: expected  `std::option::Option<amiquip::consumer::Consumer<'a>>`
              found  `std::option::Option<amiquip::consumer::Consumer<'_>>`
@kaxap
Copy link
Author

kaxap commented Jun 28, 2020

I believe Consumer and Queue have different lifetimes from SomeConsumer?
When I create channel outside of new() and pass it as an argument with 'a lifetime, then everything works. But I want to have it in one function.

@jgallagher
Copy link
Collaborator

Yes, this is just a general pain point with structs and lifetimes; a struct cannot (easily) contain both an item and a reference to that item.

I haven't tried, but you might be able to accomplish this with rental or owning_ref, which allow bundling of an owner and a reference to it together.

@davidhorac3
Copy link

davidhorac3 commented Dec 24, 2020

Did anyone found a doable solution to this? Wanted to give this library a try, but this is a deal breaker I got stuck on... makes it hard to manage consumers and publishers in elegant way.

@jgallagher
Copy link
Collaborator

The library currently doesn't support this kind of grouping without using something like rental or owning_ref (linked above), because a struct cannot contain references to its own fields. This is a not-too-uncommon question with Rust in general; e.g., https://stackoverflow.com/questions/32300132/why-cant-i-store-a-value-and-a-reference-to-that-value-in-the-same-struct has a very detailed answer with a description of the problem and possible solutions.

A different design would be to have Channels be wrappers around a private type held in an Rc, then Queue / Consumer / etc. could exist without lifetimes (they'd hold that same Rc'd inner type). But that isn't how I was using the library, and I wanted compile-time checks that I didn't leave any channels open by holding on to queues or consumers. I'm not aware of a good way to provide both the reference-style API and a "no references wrapped around Rc" API simultaneously, though. :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants