【问题标题】:How to transfer ownership of some heap memory out of a function?如何将一些堆内存的所有权转移出函数?
【发布时间】:2015-01-23 21:31:05
【问题描述】:

我正在尝试编写一个函数,将命令行实用程序 (image-magick) 的标准输出加载到结构的成员中。我想既然图片可能是MB,我还是尽量避免复制吧。

/// 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}

}

当调用标准库 std::io::Process::Command::output() 时,我想保留的大块堆是由标准库(或者可能是内核?)分配的。

借用检查器编译失败:

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")

这对我来说有点道理;拥有我试图保留的数据块的任何东西都超出了范围,在我按值返回的结构中留下了一个悬空指针。那么我如何在返回之前将内存区域的所有权实际转移到我的结构中呢?

我已经阅读了Rust Lifetimes Manual

【问题讨论】:

  • 我的代码在这里,如果它更容易检查的话:github.com/drewm1980/rust-omr/commit/… (run "cargo test")
  • 您不应该使用盒装值吗? (我可能已经过时或完全错误)
  • @logan,delnan 建议在下面将 Vec 粘贴到我的结构中。我〜认为这算作盒装类型。我也只是尝试将 Vec 粘贴到 Box 中并将 ~that 粘贴到我的结构中。这些都不允许我在结构中保留一个指向我关心的子数组的切片。

标签: memory-management struct rust object-lifetime


【解决方案1】:

您的签名声称,无论调用者想要什么生命周期,您都可以返回Image&lt;'that_lifetime&gt;。实际上,您声称返回 Image&lt;'static&gt;,其中包含 &amp;'static [u8],即指向在整个程序执行期间存在的字节片的指针。显然,您实际从中获取字节片的ProcessOutput 不会活那么久。

内存区域的所有权属于output_unwrapped,更具体地说是output_unwrapped.output(一个Vec&lt;u8&gt;)。你需要让这两个中的一个活着。最简单的选择是将所有权授予Image:将@​​987654330@ 设为Vec&lt;u8&gt;,将其移出output_unwrapped。当然,这会对Images 的所有代码处理产生持久影响,因此这是一个设计问题。图片拥有内容是否有意义?缺乏借用是否会导致任何预期用例出现问题? (如果你不能回答这些问题,你可以继续尝试,虽然你可能要几周后才能知道。)

另一种选择是抽象所有权,即同时允许向量和切片。这甚至通过 std::borrow::Cow&lt;[u8]&gt; 类型在标准库中得到支持。您只需要决定是否值得额外的麻烦 - 特别是因为 pixels 出于某种原因(可能不是一个好的?),是公开的。

【讨论】:

  • 感谢您的回复!我仍然处于设计阶段,更不用说“学习生锈”阶段了。您的解决方案听起来应该可行。你知道一个切片是否有可能拥有一个 Vec 而仍然只指向我关心的部分?或者我应该在我的 Image 结构中同时粘贴 Vec 和像素? Vec 包含图像标题。
  • @AndrewWagner 拥有一些自有数据和指向同一个结构的借用指针并不能很好地工作。我会做的(虽然我没有足够的经验强烈推荐这个)是获取 Vec 并存储我关心的索引范围,然后在实际处理像素时动态构建一个切片v[start..end]。 (当然,这可以打包成一个不错的小助手类型。)好吧,或者硬着头皮把我关心的部分从 Vec 中复制出来。零拷贝算法给我温暖、模糊的感觉,但有时它们不值得。
  • 我认为,如果我要费心使用一种具有高脑力开销的语言来进行内存管理,我应该立即学习如何表达更有效的版本。如果“不要不必要地复制巨大的内存块”在 Rust 中真的很难表达,那么我可能会尝试提交一些关于它的错误报告。也就是说,Rust 可能需要一个全局自动释放池之类的东西来存储你知道会在你的程序生命周期内一直存在的数据。
  • 知道其他合理的选项可以让我的结构拥有从外部程序的标准输出加载的数据吗?也许是 Box 或 Rc 或 Gc 之类的?无论如何,我以后可能希望多个线程同时处理这些数据......虽然我认为这些数据将由我的 main() 上下文拥有,并且线程会借用非可变引用......
  • @AndrewWagner 这很快就超出了单个问题的范围,但简而言之:问题是整个输出的内存都放在一个分配中,但你想拥有只是区块的一部分。至少,需要保存有关块的开始(和实际大小)的知识,以便能够释放它。 ProcessOutput 将输出收集在 Vec 中,因此只要您使用 ProcessOutput,就无法绕过 Vec。对于多个线程之间的共享,您应该使用Arc&lt;..&gt;(假设 all 线程具有只读访问权限)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-10-10
  • 1970-01-01
  • 2015-03-31
  • 1970-01-01
  • 2015-05-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多