Stebalien / tempfile

Temporary file library for rust
http://stebalien.com/projects/tempfile-rs
Apache License 2.0
1.2k stars 120 forks source link

Add stored parent_dir builder parameter #308

Open bjackman opened 2 weeks ago

bjackman commented 2 weeks ago

I have a usecase where it's useful to be able to pass Builders around and have them remember the parent directory to create files in, just like they remember the filename prefix and suffix.

So, add a new parent_dir API to do just that.

Note that we still call env::temp_dir even when the result won't be used. Using self.dir.unwrap_or_else(|| temp_dir()) doesn't work because tempdir_in doesn't accept an owned PathBuf. If anyone prefers, we can easily get rid of this pointless call by just having an explicit match instead of unwrap_or, but it's a bit more verbose. No strong feelings either way from my side.

bjackman commented 2 weeks ago

After writing this and then trying it out (see example usage here) I realised that

a) Unless I'm mistaken this is an incompatible change because it added a new lifetime parameter to Builder.

b) Because the Builder is not capable of owning its prefix/suffix/parent_dir params, passing the builder objects around is kinda unwieldy anyway (you'll see that my lazy approach in the linked example was just to leak them so I can set the lifetime params to 'static).

But, I'm pretty new to Rust, so I thought I'd send this PR despite these problems, in case it turns out to be useful anyway e.g. because I'm misunderstanding these issues.

Stebalien commented 2 weeks ago

Ah, I missed your comment.

a) Unless I'm mistaken this is an incompatible change because it added a new lifetime parameter to Builder.

Yes.

b) Because the Builder is not capable of owning its prefix/suffix/parent_dir params, passing the builder objects around is kinda unwieldy anyway (you'll see that my lazy approach in the linked example was just to leak them so I can set the lifetime params to 'static).

Also yes. It's possible to pass it around, but a bit awkward. It would be possible to erase the lifetime entirely by making the builder own the prefix etc.... but I'm trying to reduce allocations a bit. The builder is designed to be an ephemeral way to construct temporary files/directories.

bjackman commented 2 weeks ago

Thanks for the input :) So I think this feature is probably not worth it as-is, I think anyone who would benefit from it is pretty likely gonna want to end up creating their own wrapper type anyway which is what I ended up going for in my case.

Maybe if it was a part of a broader evolution it might make sense though. It's unclear to me how feasible it is to create something like the builder but which is generic wrt whether it owns its params or just has a reference. My understanding is that Borrow is the "I don't care if it's a reference" type but presumably it's not really intended that you store Borrows (surely the borrow checker isn't able to reason about "this field might or might not be a reference with a lifetime I need to check"?).

Do you think I'm on the right lines there? In that case I think I should abandon this effort. (BTW also I'm mostly just shooting the breeze at this point, if you're too busy to help random strangers understand Rust API design principles please feel free to ignore me!)

bjackman commented 1 week ago

Learned something interesting, I ran out of time to fully explore it so I'll just drop some notes...

It's unclear to me how feasible it is to create something like the builder but which is generic wrt whether it owns its params or just has a reference.

I think Cow might be one way to do this? I found an example of this in a random web search. But, I'm not sure if it's possible to take advantage of it here. I naïvely thought we'd be able to have something like:

pub struct Builder<'a, 'b> {
    // ...
    prefix: Cow<'a, OsStr>,
    suffix: Cow<'b OsStr>,
    // ...
}

impl<'a, 'b> Builder<'a, 'b> {
    pub fn new() -> Self {
           Self::default()
    }

    pub fn owned_prefix(&mut self, prefix: OsString) -> &mut Builder<'static, 'b> {
        self.prefix = Cow::Owned(prefix);
        self
    }
}

But this turns out to fall afoul of some pretty hardcore type system business:

 1  error: lifetime may not live long enough
    --> src/lib.rs:290:9
     |
 197 | impl<'a, 'b> Builder<'a, 'b> {
     |      -- lifetime `'a` defined here
 ...
 290 |         self
     |         ^^^^ returning this value requires that `'a` must outlive `'static`
     |
     = note: requirement occurs because of a mutable reference to `Builder<'_, '_>`
     = note: mutable references are invariant over their type parameter
     = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance

 error: could not compile `tempfile` (lib test) due to 1 previous error

But I think the hardcore stuff about variance is probably a red herring and this is just me expecting the borrow checker to do impossible magic, if you have prefix_owned(mut self, prefix: OsString) -> Builder<'static, 'b> you still get a similar error:

 1  error: lifetime may not live long enough
    --> src/lib.rs:290:9
     |
 197 | impl<'a, 'b> Builder<'a, 'b> {
     |      -- lifetime `'a` defined here
 ...
 290 |         self
     |         ^^^^ returning this value requires that `'a` must outlive `'static`

 error: could not compile `tempfile` (lib test) due to 1 previous error

I think I need to meditate at the top of a mountain and understand that error then I'll be able to understand if there's actually a solution here or not.

Stebalien commented 1 week ago

I played with that and got it working on my local "eventual v4 branch" but... it's not worth it, IMO. What I did was:

  1. Allow one to set a prefix/suffix/dir as usual.
  2. Call a special to_static method returning a Builder with a static lifetime.

Unfortunately, that static builder has all kind of thorns because, e.g., calling .prefix(...) on it would require a prefix with a static lifetime.

IMO, the right approach is to pass around a closure or a custom temporary file creator.

Stebalien commented 1 week ago

(although I may consider in an eventual v4)