1

I've narrowed this as much as I can. I have a vector of references to Senders and want to process them after receive() has completed running. However I'm running into a lifetime management issue. The inner struct is Mutex protected and contains the Senders that I'm trying to reference. I use the Mutex to gain mutability of the data, I'm open to alternatives. Any suggestions on how to work around this? I'd prefer not to change the signature of receive().

use std::sync::Mutex;
use std::sync::mpsc;

#[derive(Default)]
struct SaveForLater<'a> {
    queue: Vec<(&'a mpsc::Sender<usize>, usize)>,
}
impl<'a> SaveForLater<'a> {
    fn send(&mut self, channel: &'a mpsc::Sender<usize>, value: usize) {
        self.queue.push((channel, value));
    }
}

#[derive(Default)]
struct Forwarder {
    data: Mutex<ForwarderData>,
}
#[derive(Default)]
struct ForwarderData {
    senders: Vec<mpsc::Sender<usize>>,
}

impl Forwarder {
    fn with_capacity(capacity: usize) -> Self {
        let mut senders = Vec::new();
        for _ in 0..capacity {
            let (s,r) = mpsc::channel();
            senders.push(s);
        }
        let data = ForwarderData { senders };
        let data = Mutex::new(data);
        Self { data }
    }

    fn receive<'a>(&'a self, value: usize, sender: &mut SaveForLater<'a>) {
        match value {
            0 => { self.data.lock().unwrap().senders.drain(..); },
            _ => {
                let data = self.data.lock().unwrap();
                sender.send(&data.senders[0], value); },
/*
error[E0597]: `data` does not live long enough
  --> src/main.rs:40:30
   |
35 |     fn receive<'a>(&'a self, value: usize, sender: &mut SaveForLater<'a>) {
   |                -- lifetime `'a` defined here
...
40 |                 sender.send(&data.senders[0], value); },
   |                 -------------^^^^-------------------  - `data` dropped here while still borrowed
   |                 |            |
   |                 |            borrowed value does not live long enough
   |                 argument requires that `data` is borrowed for `'a`
*/
        }
    }
}

fn main() {
    let fwd = Forwarder::with_capacity(3);
    {
        let mut sender = SaveForLater::default();
        let value: usize = 42;
        fwd.receive(value, &mut sender);
        for (s,v) in sender.queue {
            s.send(v);
        }   
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_main() {
        main();
    }
}

see error in playground

Ultrasaurus
  • 3,031
  • 2
  • 33
  • 52
Bruce
  • 503
  • 3
  • 13

1 Answers1

1

Mutex is a distraction here; the real problem is trying to store references in structs. Let's look at the documentation of mpsc::Sender to see how it's supposed to be used:

The sending-half of Rust's asynchronous channel type. This half can only be owned by one thread, but it can be cloned to send to other threads.

Sender does not even implement Sync, so even if you make the borrow checker happy you won't be able to share a single one between multiple threads. It's meant to be cloned instead:

struct SaveForLater {
    queue: Vec<(mpsc::Sender<usize>, usize)>,
}
    fn receive(&self, value: usize, sender: &mut SaveForLater) {
        // ...
                sender.send(data.senders[0].clone(), value);
                //                         ^^^^^^^^

Here's a full example. No lifetime parameters are necessary.

In general you should avoid putting references in structs because it can only be done safely if you are very diligent about proving the lifetime relationships at compile time. In most cases you should simply clone the data, or if that's the wrong semantic, use Arc or Rc. mpsc::Sender uses Arc internally to be cheap to clone.

trent
  • 25,033
  • 7
  • 51
  • 90
  • I used mpsc::Sender as a simplification. The actual code is using smol::channel::Sender, I was hoping to avoid the overhead of clone(), either Arc or Sender. – Bruce Mar 07 '21 at 02:07
  • 1
    The borrow checker fundamentally does *static lifetime analysis*, and you have data with lifetimes that *can't be analyzed statically* because they depend on runtime behavior. If you're not going to store all the `Sender`s in an arena, or a pre-filled `Vec` or something (meaning they all get destroyed at once), you **need** runtime checking of some kind (or you can just leak memory). It's not overhead if you need it. – trent Mar 07 '21 at 03:19
  • Ah, yes, that's it -- it does static lifetime analysis, and of course I look at it and say its fine, but only because I'm looking at the code structure and usage, essentially doing runtime analysis. Thanks for setting me head straight. – Bruce Mar 07 '21 at 21:32
  • Exactly! Yes, I think the Rust compiler sometimes *seems* magical enough that we can be disappointed when it refuses to perform *literal magic* when asked. Anyway, that's happened to me on multiple occasions. – trent Mar 07 '21 at 21:59
  • So, that's fixed... Now I just have to figure out how to resurrect a concrete type from an Any that I stored in a HashMap :( – Bruce Mar 07 '21 at 22:04