Generic specialization, F: Write but do more when a File?

Hello, I'm writing a structure that I'd like to be able to take any handle that implements std::io::Write (for instance, std::io::Cursor and std::fs::File), but which will call sync_all() if the handle happens to be a File. I thought that Any::downcast_ref would be the thing for this, but the dependence on a 'static lifetime causing me trouble (and is undesirable).

I don't have any attachment to downcast_ref, I'm happy with anything that would let me store a generic Write, but call sync_all() when possible.

Sample code:

struct Journal<F> where F: Any + Write {
    fh: F,
}

impl<F> Journal<F> where F: Any + Write {
    pub fn new(mut fh: F) -> Journal<F> { Ok(Journal { fh }) }

    pub fn write(&mut self, data: &str) -> std::io::Result<()> {
        // Minimal write example (actual is more interesting)
        self.fh.write(data.as_bytes())?;
        let any = &self.fh as &dyn Any;
        match any.downcast_ref::<File>() {
            Some(fh) => { fh.sync_all()? },
            None     => { }
        }
        Ok(())
    }
}

// // My first attempt tried using two impl blocks (one generic, one for File)
// impl Journal<File> {
//     pub fn write(&mut self, data: &str) -> std::io::Result<()> {
//         // Minimal write example (actual is more interesting)
//         self.fh.write(data.as_bytes())?;
//         self.fh.sync_all()?;
//         Ok(())
//     }
// }

// sample test usage:
#[test]
fn test_journal_curse() {
    let mut buf: [u8; 1024] = [0; 1024];

    // Build a journal entry and its expected result:
    let res = {
        let curs = Cursor::new(&mut buf[..]);
        //                     ^borrowed value does not live long enough
        //                      cast requires that `buf` is borrowed for `'static`
        let mut journal = Journal::new(curs).unwrap();
        journal.write("1");
        journal.write("4");

        "14".to_string()
    };

    let rbuf = &buf[0..res.len()];
    assert_eq!(String::from_utf8(Vec::from(rbuf)).unwrap(), res);
}

#[test]
fn test_journal_file() {
    let mut fh = File::create("foo.txt").unwrap();
    let mut journal = Journal::new(fh).unwrap();
    journal.write("1"); // immediate sync
    journal.write("4"); // immediate sync
}

My first attempt tried a split impl<F> Journal<F> where F: Write { ... } and impl Journal<File>, but that gave a duplicate definition error.

Using downcast_ref I get errors in test_journal_curse() that my buffer doesn't live long enough (also errors that I'm borrowing both mutably and immutably since the 'static scope breaks out of the block constructing res.

Thanks!

You can probably fix your code by adding + 'static to your where bounds for F.

However you should also consider this option: playground

1 Like

Ah, I see – yes, the new trait seems the better approach and works great! Thanks!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.