0

I am trying to create a Stream using a camera with a blocking capture method. The blocking call is wrapped with blocking::unblock.

use futures::stream;
use rscam::{Camera, Config};

fn frame_stream() -> impl stream::Stream {
    let mut camera = Camera::new("/dev/video0").unwrap();

    camera.start(&Config {
        interval: (1, 30),
        resolution: (1280, 720),
        format: b"H264",
        ..Default::default()
    }).unwrap();

    stream::unfold(camera, |c| async move {
        let frame = blocking::unblock(|| c.capture().unwrap()).await;
        
        Some((frame, c))
    })
}

Compiling gives this error message:

error[E0373]: closure may outlive the current function, but it borrows `c`, which is owned by the current function
  --> src/lib.rs:15:39
   |
15 |         let frame = blocking::unblock(|| c.capture().unwrap()).await;
   |                                       ^^ - `c` is borrowed here
   |                                       |
   |                                       may outlive borrowed value `c`
   |
note: function requires argument type to outlive `'static`
  --> src/lib.rs:15:21
   |
15 |         let frame = blocking::unblock(|| c.capture().unwrap()).await;
   |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: to force the closure to take ownership of `c` (and any other referenced variables), use the `move` keyword
   |
15 |         let frame = blocking::unblock(move || c.capture().unwrap()).await;
   |                                       ++++

error[E0505]: cannot move out of `c` because it is borrowed
  --> src/lib.rs:17:22
   |
15 |         let frame = blocking::unblock(|| c.capture().unwrap()).await;
   |                     ------------------------------------------
   |                     |                 |  |
   |                     |                 |  borrow occurs due to use in closure
   |                     |                 borrow of `c` occurs here
   |                     argument requires that `c` is borrowed for `'static`
16 |         
17 |         Some((frame, c))
   |                      ^ move out of `c` occurs here

How can I guarantee to the compiler that the reference to c taken in the closure will still be valid? I assume it will be, since execution of the closure is awaited before c is returned.

Solution

stream::unfold(camera, |c| async move {
    Some(
        blocking::unblock(|| {
            (c.capture().unwrap(), c)
        }).await
    )
})
  • Please include the full error as seen by `cargo check` and include the missing definitions or imported crate items (like `Camera` and `blocking`). Its hard to answer without having all the pieces. – kmdreko Jul 02 '22 at 20:21
  • It didn't seem that relevant, but here you go. – Stonks3141 Jul 02 '22 at 20:34
  • 1
    `blocking::unblock` explicitly requires that the passed-in function is valid for the static lifetime, so it won't be able to borrow `c`. There's not much of a way around it other than storing `c` in an `Arc`. – Colonel Thirty Two Jul 02 '22 at 21:29

2 Answers2

1

You could move the camera into the inner closure, then return it once the frame capture is complete:

    stream::unfold(camera, |c| async move {
        Some(blocking::unblock(|| move {
            let frame = c.capture().unwrap()).await;
            (frame,c)
        })
    })
harmic
  • 28,606
  • 5
  • 67
  • 91
0

.await does not guarantee liveness. This is the general problem of scoped async tasks. Futures can be canceled at any time. Consider:

let future = async {
    let local = 123;
    blocking::unblock(|| local).await;
};

// Poll `future`, but only once.
futures::block_on(async move { futures::poll!(future) });

We started a task using local data, then dropped the future. The task continues executing but the local data is gone. For this reason, there is currently no sound way to expose an async API allowing using local data similar to scoped threads. You have to use 'static data, for example by wrapping in Arc.

See also blocking issue #4.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77