How to transfer ownership of some heap memory out of a function?

2.7k views Asked by At

I am trying to write a function that loads the standard output of a command-line utility (image-magick) into a member of a struct. I figure that since images can be may MB, I might as well avoid copies as much as possible.

/// An image data container used internally.
/// Images are 8-bit single channel for now.
pub struct Image<'a> {
    /// Width of the image in pixels.
    pub width: u32,
    /// Height of the image in pixels.
    pub height: u32,
    /// The buffer containing the image data, as a slice.
    pub pixels: &'a [u8],
}

// Load a file by first using imageMagick to convert it to a .pgm file.
fn load_using_magick<'a>(path: Path) -> Image<'a> {
    use std::io::Command;

    let output:IoResult<ProcessOutput> = Command::new("convert")
        .arg("-format pgm")
        .arg("-depth 8")
        .arg(path)
        .arg("-")
        .output();
    let output_unwrapped:ProcessOutput = match output {
        Ok(o) => o,
        Err(e) => panic!("Unable to run ImageMagick's convert tool in a separate process! convert returned: {}", e),
    };

    let bytes: &[u8] = match output_unwrapped.status.success() {
        false => panic!("signal or wrong error code from sub-process?"),
        true => output_unwrapped.output.as_slice(),
    };
    // Note, width and height will eventually get parsed out of "bytes"
    // and the returned Imaeg.pixels will point to a slice of the (already)
    // heap allocated memory.  I just need to figure out ownership first...
    Image{width:10,height:10,pixels:bytes}

}

The big chunk of heap that I would like to keep around was allocated by the standard library (or maybe the kernel?) when the standard library std::io::Process::Command::output() is called.

Compilation is failing with the borrow checker:

src/loaders.rs:41:17: 41:40 error: `output_unwrapped.output` does not live long enough
src/loaders.rs:41         true => output_unwrapped.output.as_slice(),
                                  ^~~~~~~~~~~~~~~~~~~~~~~
src/loaders.rs:21:51: 48:2 note: reference must be valid for the lifetime 'a as defined on the block at 21:50...
src/loaders.rs:21 fn load_using_magick<'a>(path: Path) -> Image<'a> {
src/loaders.rs:22     use std::io::Command;
src/loaders.rs:23 
src/loaders.rs:24     let output:IoResult<ProcessOutput> = Command::new("convert")
src/loaders.rs:25         .arg("-format pgm")
src/loaders.rs:26         .arg("-depth 8")
                  ...
src/loaders.rs:21:51: 48:2 note: ...but borrowed value is only valid for the block at 21:50
src/loaders.rs:21 fn load_using_magick<'a>(path: Path) -> Image<'a> {
src/loaders.rs:22     use std::io::Command;
src/loaders.rs:23 
src/loaders.rs:24     let output:IoResult<ProcessOutput> = Command::new("convert")
src/loaders.rs:25         .arg("-format pgm")
src/loaders.rs:26         .arg("-depth 8")

This makes some sense to me; whatever is owning the chunk of data I'm trying to keep around is going out of scope, leaving a dangling pointer in the struct I returned by value. So how do I actually transfer ownership of the memory region to my struct before I return it?

I already read the Rust Lifetimes Manual.

1

There are 1 answers

7
AudioBubble On BEST ANSWER

Your signature claims that, whatever lifetime the caller desires, you can return an Image<'that_lifetime>. In effect, you are claiming to return Image<'static>, which contains a &'static [u8], i.e. a pointer to a byte slice that lives for the entire duration of the program execution. Obviously, the ProcessOutput from which you actually take the byte slice does not live that long.

The ownership of the memory region belongs to output_unwrapped, more specifically output_unwrapped.output (a Vec<u8>). You need to keep one of those two alive. The easiest option is to give the ownership to Image: Make pixels a Vec<u8>, which you move out of output_unwrapped. Of course, this has lasting impact on all code handling Images, so this is a design issue. Does it make sense for the image to own the contents? Does lack of borrowing cause problems for any intended use cases? (If you can't answer these questions, you can always go ahead and try it, though you may only find out weeks later.)

One alternative is to abstract over ownership, i.e. allow both vectors and slices. This is even supported in the standard library via the std::borrow::Cow<[u8]> type. You just have to decide if it's worth the extra trouble — especially since pixels is, for some reason (probably not a good one?), public.