0

playground

I'm trying to build an abstraction over a file, where the content is stored in memory and a hash is computed before writing it out to the filesystem, which should happen in the close() method.

use std::path::PathBuf;

use sha2::{Digest, Sha256};

fn main() {
    let mut fwh = FileWithHash::new(PathBuf::from("somepath.txt"));
    fwh.write("first line\n");
    fwh.write("second line\n");
    fwh.close();
}
struct FileWithHash {
    path: PathBuf,
    hash: Option<String>,
    content: Vec<String>,
    pub hasher: Sha256,
}

impl FileWithHash {
    pub fn new(path: PathBuf) -> FileWithHash {
        FileWithHash {
            path,
            hash: None,
            content: Vec::new(),
            hasher: Sha256::new(),
        }
    }

    pub fn write(&mut self, content: &str) {
        self.hasher.update(content.as_bytes());
        self.content.push(content.to_string());
    }

    pub fn close(&mut self) {
        // compute the final hash
        // signature: fn finalize(self) -> Output<Self>;
        // it consumes self
        // error: cannot move out of `self.hasher` which is behind a mutable reference
        self.hash = Some(format!("{:x}", self.hasher.finalize()));
        // write the file the path
        // ...
    }
}

the problem I have is that the self.hasher.finalize() method consumes the hasher which is part of self, which is itself &mut. I think I understand why this doesn't work, but I cannot come up with a reasonable fix.

I've tried extracting the logic into a function like

    pub fn compute_hash(hasher: Sha256) -> String {
        format!("{:x}", hasher.finalize())
    }

but then I run into issues with partially moved values (makes sense).

Any ideas about what the proper pattern should be here?

Thanks for your help.


@justinas, I've tried the Option suggestion.

the methods become

pub fn write(&mut self, content: String) {
        match &mut self.hasher {
            Some(h) => h.update(content.as_bytes()),
            None => {}
        }
       
        self.size = self.size + content.len();
        self.content.push(content);
    }

    pub fn close(&mut self) {
        self.hash = Some(format!("{:x}", self.hasher.take().unwrap().finalize()));
        // write the file the path
        // ...
    }

Does this look OK to you?

user103716
  • 190
  • 12
  • Does this answer your question? [How to move one field out of a struct that implements Drop trait?](https://stackoverflow.com/questions/31307680/how-to-move-one-field-out-of-a-struct-that-implements-drop-trait) – justinas Apr 11 '21 at 19:51
  • Application of the duplicate: either `replace()` the hasher with a new instance of `Sha256`, or instead make the field `Option` and [take()](https://doc.rust-lang.org/std/option/enum.Option.html#method.take) the hasher from it, leaving a `None` in its place. – justinas Apr 11 '21 at 19:52
  • 1
    Is a `FileWithHash` supposed to remain usable after `close()` has been called? If not, you could change the signature of `close()` so that it takes `mut self`. That way move would no longer be behind a mutable reference and the [code would compile](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f901c461d6d7816485f76a634e32dc82). – user4815162342 Apr 12 '21 at 07:17

0 Answers0