V4L2 in Rust: what forbid(unsafe_code) cost me, and what it bought
Rust is billed as a memory-safe language. Every talk, every marketing page, every keynote leans on that. So when I added unsafe_code = "forbid" to pi-cam-capture's [lints.rust] in Cargo.toml, I meant it. Not "as much as possible." Not "except at FFI boundaries." Forbidden.
About a week into the streaming layer, the borrow checker made me pay for that decision. And, on the way through, wrote a better API than the one I was about to ship.
This one's a side note from the Glass-to-Glass series1, not part of the main capture engine article. The main-line piece2 shows the final CaptureSession and moves on. This is the design debate that led there.
What V4L2 streaming actually needs
V4L2 memory-mapped streaming is a kernel-side resource. You ask the driver to allocate a fixed pool of buffers, you mmap them into userspace, you queue them, you VIDIOC_STREAMON, and then you drain filled buffers in a tight loop and requeue. When you're done, VIDIOC_STREAMOFF, then munmap everything. The v4l crate wraps that faithfully, and its Stream<'a> takes a &Device, because a stream cannot exist without the device whose buffers it holds.
The whole reason V4L2 has this shape is amortization. Allocation is expensive. Mapping is expensive. The steady state is supposed to be one DQBUF, process, QBUF, repeat. Everything else is setup and teardown.
The point I need for the rest of this article is smaller: Stream<'a> borrows the device, and any wrapper above v4l that tries to hide both behind a single type walks straight into a self-referential struct.