kangalio
kangalio

Reputation: 673

Conflicting lifetime requirements when forwarding slices from a static callback into a channel

I am using a Rust Midi library to receive and handle real-time Midi messages. It exposes a connect function that accepts a callback which will be called for every Midi message that arrives. My plan was to forward these Midi messages to a channel. This is the minimal version of the code that still reproduces my issue (Rust Playground link):

use std::sync::mpsc;

fn main() {
    let (tx, rx) = mpsc::sync_channel(0);

    // The callback forwards all data it gets to the channel
    connect(|data| tx.send(data).unwrap());

    // `rx` will be given to some other part of the program here
}

// This is basically the function signature of my Midi library's `connect` function
// I *don't have control over it*, as it's part of that external library
fn connect<F>(callback: F)
        where F: FnMut(&[u8]) + Send + 'static {}

After researching this issue for a while, I thought the solution was to add the move keyword to the callback. This made sense to me, as the callback may live longer than the main function so tx may have been dropped when the callback still needs it. move forces the callback to capture its environment by value which should lead to tx living exactly as long as the callback.
Yet move changes nothing at all; the error message stays the same.

I noticed that when I change the parameter of the callback from &[u8] to just u8, move actually does the trick. I have no idea why this could be.

Another question I've found explains how to send mutable slices over channels, but I have immutable slices, so I assume there is an easier solution than the one that has been explained there.

To close this off: I know that it's possible to restructure the code to avoid channels. However, I'm still interested in the solution so I can solve future issues with channels and callbacks on my own.

Upvotes: 1

Views: 108

Answers (1)

kangalio
kangalio

Reputation: 673

It clicked inside my brain.

Additionally, to the move keyword, the following is required:

connect provides a reference to an array which lives for the scope of the callback. That means that when the callback finishes, the &[u8] is inaccessible. I tried sending the reference outside of the callback which made no sense because then it would have to live longer.

The solution is to create an owned object from the slice, like a Vec. That's trivially done by adding .to_vec():

connect(move |data| tx.send(data.to_vec()).unwrap());

The Vec can be passed around freely without any lifetime conflictions. :)

Upvotes: 1

Related Questions