Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add stored parent_dir builder parameter #308

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

bjackman
Copy link

@bjackman bjackman commented Nov 9, 2024

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.

I have a usecase where it's useful to be able to pass `Builder`s 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
Copy link
Author

bjackman commented Nov 9, 2024

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.

Copy link
Owner

@Stebalien Stebalien left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems reasonable to me.

@@ -461,7 +485,7 @@ impl<'a, 'b> Builder<'a, 'b> {
/// [security]: struct.NamedTempFile.html#security
/// [resource-leaking]: struct.NamedTempFile.html#resource-leaking
pub fn tempfile(&self) -> io::Result<NamedTempFile> {
self.tempfile_in(env::temp_dir())
self.tempfile_in(self.dir.unwrap_or(&env::temp_dir()))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a documentation update here and below.

@@ -304,6 +306,28 @@ impl<'a, 'b> Builder<'a, 'b> {
self
}

/// Set a directory to create files in.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Set a directory to create files in.
/// Set the default directory in which temporary files/directories will be created.

@@ -171,29 +171,31 @@ pub use crate::spooled::{spooled_tempfile, SpooledData, SpooledTempFile};

/// Create a new temporary file or directory with custom parameters.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Builder<'a, 'b> {
pub struct Builder<'a, 'b, 'c> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change. IMO, I messed up and should have had just one lifetime here (relying on sub-typing) but fixing that would also be a breaking change.

We can do one of:

  1. Re-use one of the other lifetimes.
  2. Store the directory by-value (although I'd still take the parameter as AsRef).

I'd go with the former approach (probably using the prefix's lifetime, 'a. It's a bit awkward but it provides the best upgrade path for an eventual v4.

@Stebalien
Copy link
Owner

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
Copy link
Author

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
Copy link
Author

bjackman commented Nov 16, 2024

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
Copy link
Owner

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
Copy link
Owner

(although I may consider in an eventual v4)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants