Skip to main content

async_tempfile/
tempfile.rs

1use std::borrow::{Borrow, BorrowMut};
2use std::fmt::{Debug, Formatter};
3use std::io::{ErrorKind, IoSlice, SeekFrom};
4use std::ops::{Deref, DerefMut};
5use std::path::{Path, PathBuf};
6use std::pin::Pin;
7use std::sync::Arc;
8use std::task::{Context, Poll};
9use tokio::fs::{File, OpenOptions};
10use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
11
12#[cfg(not(feature = "uuid"))]
13use crate::random_name::RandomName;
14use crate::{AtomicOwnership, Error, Ownership, PersistError};
15#[cfg(feature = "uuid")]
16use uuid::Uuid;
17
18pub(crate) const FILE_PREFIX: &str = "atmp_";
19
20/// Maximum number of attempts to find a free name when creating a file with a
21/// randomly generated, collision-resistant name.
22const MAX_NAME_ATTEMPTS: usize = 16;
23
24/// How the underlying file should be opened or created.
25#[derive(Copy, Clone, Eq, PartialEq)]
26enum CreateMode {
27    /// Create a brand-new file, failing if it already exists (`O_EXCL`).
28    /// Used for unpredictable, auto-generated names to avoid clobbering an
29    /// existing file or following a planted symlink.
30    Exclusive,
31    /// Create the file, or open it if it already exists. Used for user-supplied
32    /// names, preserving historic behavior.
33    CreateOrOpen,
34    /// Open an existing file without creating it. Used by `from_existing`.
35    OpenExisting,
36}
37
38/// A named temporary file that will be cleaned automatically
39/// after the last reference to it is dropped.
40pub struct TempFile {
41    /// A local reference to the file. Used to write to or read from the file.
42    ///
43    /// Field order matters: `file` is declared before `core` so that this local
44    /// handle is dropped (closed) before the shared `core` is released and the
45    /// file is deleted. Required for correct deletion on Windows, which refuses
46    /// to delete a file while a handle to it is open.
47    file: File,
48
49    /// A shared pointer to the owned (or non-owned) file.
50    /// The `Arc` ensures that the enclosed file is kept alive
51    /// until all references to it are dropped.
52    core: Arc<TempFileCore>,
53}
54
55/// The instance that tracks the temporary file.
56/// If dropped, the file will be deleted.
57struct TempFileCore {
58    /// The path of the contained file.
59    path: PathBuf,
60
61    /// Whether the file specified in `path` is owned (and deleted on drop) or
62    /// merely borrowed. Stored atomically because it is read from `Drop`, which
63    /// must never block on a lock, and mutated by `keep`/`persist`/`drop_async`.
64    ownership: AtomicOwnership,
65}
66
67impl TempFile {
68    /// Creates a new temporary file in the default location.
69    /// When the instance goes out of scope, the file will be deleted.
70    ///
71    /// ## Example
72    ///
73    /// ```
74    /// # use async_tempfile::{TempFile, Error};
75    /// # use tokio::fs;
76    /// # let _ = tokio_test::block_on(async {
77    /// let file = TempFile::new().await?;
78    ///
79    /// // The file exists.
80    /// let file_path = file.file_path().clone();
81    /// assert!(fs::metadata(file_path.clone()).await.is_ok());
82    ///
83    /// // Deletes the file.
84    /// drop(file);
85    ///
86    /// // The file was removed.
87    /// assert!(fs::metadata(file_path).await.is_err());
88    /// # Ok::<(), Error>(())
89    /// # });
90    /// ```
91    pub async fn new() -> Result<Self, Error> {
92        Self::new_in(Self::default_dir()).await
93    }
94
95    /// Creates a new temporary file in the default location.
96    /// When the instance goes out of scope, the file will be deleted.
97    ///
98    /// ## Arguments
99    ///
100    /// * `name` - The name of the file to create in the default temporary directory.
101    ///
102    /// ## Example
103    ///
104    /// ```
105    /// # use async_tempfile::{TempFile, Error};
106    /// # use tokio::fs;
107    /// # let _ = tokio_test::block_on(async {
108    /// let file = TempFile::new_with_name("new_with_name_example.file").await?;
109    ///
110    /// // The file exists.
111    /// let file_path = file.file_path().clone();
112    /// assert!(fs::metadata(file_path.clone()).await.is_ok());
113    ///
114    /// // Deletes the file.
115    /// drop(file);
116    ///
117    /// // The file was removed.
118    /// assert!(fs::metadata(file_path).await.is_err());
119    /// # Ok::<(), Error>(())
120    /// # });
121    /// ```
122    pub async fn new_with_name<N: AsRef<str>>(name: N) -> Result<Self, Error> {
123        Self::new_with_name_in(name, Self::default_dir()).await
124    }
125
126    /// Creates a new temporary file in the default location.
127    /// When the instance goes out of scope, the file will be deleted.
128    ///
129    /// ## Arguments
130    ///
131    /// * `uuid` - A UUID to use as a suffix to the file name.
132    ///
133    /// ## Example
134    ///
135    /// ```
136    /// # use async_tempfile::{TempFile, Error};
137    /// # use tokio::fs;
138    /// # let _ = tokio_test::block_on(async {
139    /// let id = uuid::Uuid::new_v4();
140    /// let file = TempFile::new_with_uuid(id).await?;
141    ///
142    /// // The file exists.
143    /// let file_path = file.file_path().clone();
144    /// assert!(fs::metadata(file_path.clone()).await.is_ok());
145    ///
146    /// // Deletes the file.
147    /// drop(file);
148    ///
149    /// // The file was removed.
150    /// assert!(fs::metadata(file_path).await.is_err());
151    /// # Ok::<(), Error>(())
152    /// # });
153    /// ```
154    #[cfg_attr(docsrs, doc(cfg(feature = "uuid")))]
155    #[cfg(feature = "uuid")]
156    pub async fn new_with_uuid(uuid: Uuid) -> Result<Self, Error> {
157        Self::new_with_uuid_in(uuid, Self::default_dir()).await
158    }
159
160    /// Creates a new temporary file in the specified location.
161    /// When the instance goes out of scope, the file will be deleted.
162    ///
163    /// The file is created with a collision-resistant, unpredictable name using
164    /// an exclusive (`O_EXCL`) create, so it never clobbers an existing file.
165    ///
166    /// ## Crate Features
167    ///
168    /// * `uuid` - When the `uuid` crate feature is enabled, a random UUIDv4 is used to
169    ///   generate the temporary file name.
170    ///
171    /// ## Arguments
172    ///
173    /// * `dir` - The directory to create the file in.
174    ///
175    /// ## Example
176    ///
177    /// ```
178    /// # use async_tempfile::{TempFile, Error};
179    /// # use tokio::fs;
180    /// # let _ = tokio_test::block_on(async {
181    /// let path = std::env::temp_dir();
182    /// let file = TempFile::new_in(path).await?;
183    ///
184    /// // The file exists.
185    /// let file_path = file.file_path().clone();
186    /// assert!(fs::metadata(file_path.clone()).await.is_ok());
187    ///
188    /// // Deletes the file.
189    /// drop(file);
190    ///
191    /// // The file was removed.
192    /// assert!(fs::metadata(file_path).await.is_err());
193    /// # Ok::<(), Error>(())
194    /// # });
195    pub async fn new_in<P: Borrow<Path>>(dir: P) -> Result<Self, Error> {
196        Self::create_with_affixes(dir.borrow(), FILE_PREFIX, "").await
197    }
198
199    /// Creates a new temporary file in the specified location.
200    /// When the instance goes out of scope, the file will be deleted.
201    ///
202    /// ## Arguments
203    ///
204    /// * `dir` - The directory to create the file in.
205    /// * `name` - The file name to use.
206    ///
207    /// ## Example
208    ///
209    /// ```
210    /// # use async_tempfile::{TempFile, Error};
211    /// # use tokio::fs;
212    /// # let _ = tokio_test::block_on(async {
213    /// let path = std::env::temp_dir();
214    /// let file = TempFile::new_with_name_in("new_with_name_in_example.file", path).await?;
215    ///
216    /// // The file exists.
217    /// let file_path = file.file_path().clone();
218    /// assert!(fs::metadata(file_path.clone()).await.is_ok());
219    ///
220    /// // Deletes the file.
221    /// drop(file);
222    ///
223    /// // The file was removed.
224    /// assert!(fs::metadata(file_path).await.is_err());
225    /// # Ok::<(), Error>(())
226    /// # });
227    /// ```
228    pub async fn new_with_name_in<N: AsRef<str>, P: Borrow<Path>>(
229        name: N,
230        dir: P,
231    ) -> Result<Self, Error> {
232        let dir = dir.borrow();
233        if !crate::path_is_dir(dir).await {
234            return Err(Error::InvalidDirectory);
235        }
236        let path = dir.join(name.as_ref());
237        Self::new_internal(path, Ownership::Owned, CreateMode::CreateOrOpen).await
238    }
239
240    /// Creates a new temporary file in the specified location.
241    /// When the instance goes out of scope, the file will be deleted.
242    ///
243    /// ## Arguments
244    ///
245    /// * `dir` - The directory to create the file in.
246    /// * `uuid` - A UUID to use as a suffix to the file name.
247    ///
248    /// ## Example
249    ///
250    /// ```
251    /// # use async_tempfile::{TempFile, Error};
252    /// # use tokio::fs;
253    /// # let _ = tokio_test::block_on(async {
254    /// let path = std::env::temp_dir();
255    /// let id = uuid::Uuid::new_v4();
256    /// let file = TempFile::new_with_uuid_in(id, path).await?;
257    ///
258    /// // The file exists.
259    /// let file_path = file.file_path().clone();
260    /// assert!(fs::metadata(file_path.clone()).await.is_ok());
261    ///
262    /// // Deletes the file.
263    /// drop(file);
264    ///
265    /// // The file was removed.
266    /// assert!(fs::metadata(file_path).await.is_err());
267    /// # Ok::<(), Error>(())
268    /// # });
269    /// ```
270    #[cfg_attr(docsrs, doc(cfg(feature = "uuid")))]
271    #[cfg(feature = "uuid")]
272    pub async fn new_with_uuid_in<P: Borrow<Path>>(uuid: Uuid, dir: P) -> Result<Self, Error> {
273        let file_name = format!("{FILE_PREFIX}{uuid}");
274        Self::new_with_name_in(file_name, dir).await
275    }
276
277    /// Wraps a new instance of this type around an existing file.
278    /// If `ownership` is set to [`Ownership::Borrowed`], this method does not take ownership of
279    /// the file, i.e. the file will not be deleted when the instance is dropped.
280    ///
281    /// ## Arguments
282    ///
283    /// * `path` - The path of the file to wrap.
284    /// * `ownership` - The ownership of the file.
285    pub async fn from_existing<P: Borrow<Path>>(
286        path: P,
287        ownership: Ownership,
288    ) -> Result<Self, Error> {
289        if !crate::path_is_file(path.borrow()).await {
290            return Err(Error::InvalidFile);
291        }
292        Self::new_internal(path, ownership, CreateMode::OpenExisting).await
293    }
294
295    /// Creates a builder for configuring a new temporary file
296    /// (prefix, suffix, directory) before creating it.
297    ///
298    /// ## Example
299    ///
300    /// ```
301    /// # use async_tempfile::{TempFile, Error};
302    /// # let _ = tokio_test::block_on(async {
303    /// let file = TempFile::builder()
304    ///     .prefix("log_")
305    ///     .suffix(".txt")
306    ///     .create()
307    ///     .await?;
308    ///
309    /// let name = file.file_path().file_name().unwrap().to_string_lossy().into_owned();
310    /// assert!(name.starts_with("log_"));
311    /// assert!(name.ends_with(".txt"));
312    /// # Ok::<(), Error>(())
313    /// # });
314    /// ```
315    pub fn builder() -> crate::TempFileBuilder {
316        crate::TempFileBuilder::new()
317    }
318
319    /// Returns the path of the underlying temporary file.
320    pub fn file_path(&self) -> &PathBuf {
321        &self.core.path
322    }
323
324    /// Opens a new TempFile instance in read-write mode.
325    pub async fn open_rw(&self) -> Result<TempFile, Error> {
326        let file = OpenOptions::new()
327            .read(true)
328            .write(true)
329            .open(&self.core.path)
330            .await?;
331        Ok(TempFile {
332            file,
333            core: self.core.clone(),
334        })
335    }
336
337    /// Opens a new TempFile instance in read-only mode.
338    pub async fn open_ro(&self) -> Result<TempFile, Error> {
339        let file = OpenOptions::new()
340            .read(true)
341            .write(false)
342            .open(&self.core.path)
343            .await?;
344        Ok(TempFile {
345            file,
346            core: self.core.clone(),
347        })
348    }
349
350    /// Creates a new TempFile instance that shares the same underlying
351    /// file handle as the existing TempFile instance.
352    /// Reads, writes, and seeks will affect both TempFile instances simultaneously.
353    pub async fn try_clone(&self) -> Result<TempFile, Error> {
354        Ok(TempFile {
355            file: self.file.try_clone().await?,
356            core: self.core.clone(),
357        })
358    }
359
360    /// Determines the ownership of the temporary file.
361    /// ### Example
362    /// ```
363    /// # use async_tempfile::{Ownership, TempFile};
364    /// # let _ = tokio_test::block_on(async {
365    /// let file = TempFile::new().await?;
366    /// assert_eq!(file.ownership(), Ownership::Owned);
367    /// # drop(file);
368    /// # Ok::<(), Box<dyn std::error::Error>>(())
369    /// # });
370    /// ```
371    pub fn ownership(&self) -> Ownership {
372        self.core.ownership.get()
373    }
374
375    /// Disables automatic deletion and returns the path of the underlying file,
376    /// turning the temporary file into a permanent one.
377    ///
378    /// This affects every clone that shares the same underlying file: none of
379    /// them will delete it when dropped.
380    ///
381    /// ## Example
382    ///
383    /// ```
384    /// # use async_tempfile::{TempFile, Error};
385    /// # use tokio::fs;
386    /// # let _ = tokio_test::block_on(async {
387    /// let file = TempFile::new().await?;
388    /// let path = file.keep();
389    ///
390    /// // The file still exists after the handle is dropped.
391    /// assert!(fs::metadata(path.clone()).await.is_ok());
392    /// # fs::remove_file(path).await.ok();
393    /// # Ok::<(), Error>(())
394    /// # });
395    /// ```
396    pub fn keep(self) -> PathBuf {
397        let path = self.core.path.clone();
398        self.core.ownership.set_borrowed();
399        path
400    }
401
402    /// Persists the temporary file by moving it to `target`, returning the new
403    /// path. The file will no longer be deleted automatically.
404    ///
405    /// The move is performed with [`tokio::fs::rename`] and therefore must stay
406    /// on the same filesystem (a cross-device move returns an error).
407    ///
408    /// On failure the temporary file is **not** deleted: it is left at its
409    /// original location and that path is returned in [`PersistError::path`], so
410    /// no data is lost on a cross-device or permission error. The caller may
411    /// re-wrap it with [`TempFile::from_existing`] to restore automatic cleanup,
412    /// or delete it. The local handle is closed before the rename so the move
413    /// also succeeds on Windows.
414    ///
415    /// ## Arguments
416    ///
417    /// * `target` - The destination path to move the file to.
418    ///
419    /// ## Example
420    ///
421    /// ```
422    /// # use async_tempfile::{TempFile, Error};
423    /// # use tokio::fs;
424    /// # let _ = tokio_test::block_on(async {
425    /// let file = TempFile::new().await?;
426    /// let target = std::env::temp_dir().join("persisted_async_tempfile.txt");
427    ///
428    /// let path = file.persist(&target).await.map_err(|e| e.error)?;
429    /// assert!(fs::metadata(path.clone()).await.is_ok());
430    /// # fs::remove_file(path).await.ok();
431    /// # Ok::<(), Error>(())
432    /// # });
433    /// ```
434    pub async fn persist<P: AsRef<Path>>(self, target: P) -> Result<PathBuf, PersistError> {
435        let target = target.as_ref().to_path_buf();
436        let TempFile { file, core } = self;
437        // Close our handle before the rename: Windows refuses to move a file
438        // while a handle to it is open.
439        drop(file);
440
441        match tokio::fs::rename(&core.path, &target).await {
442            Ok(()) => {
443                core.ownership.set_borrowed();
444                Ok(target)
445            }
446            Err(e) => {
447                // Preserve the caller's data: leave the temporary in place and
448                // report where it is, rather than deleting it on the way out.
449                core.ownership.set_borrowed();
450                Err(PersistError {
451                    error: Error::Io(e),
452                    path: core.path.clone(),
453                })
454            }
455        }
456    }
457
458    /// Asynchronously drops the TempFile, ensuring any resources are properly released.
459    /// This is useful for explicitly managing the lifecycle of the TempFile
460    /// in an asynchronous context.
461    ///
462    /// When this is the last reference to an owned file, the file is removed via
463    /// [`tokio::fs::remove_file`] without blocking the runtime. The synchronous
464    /// `Drop` remains armed as a backstop, so a cancelled or panicking
465    /// `drop_async` still cleans up the file.
466    ///
467    /// ## Example
468    ///
469    /// ```rust
470    /// # use async_tempfile::{TempFile, Error};
471    /// # let _ = tokio_test::block_on(async {
472    /// let file = TempFile::new().await?;
473    /// let path = file.file_path().to_path_buf();
474    /// assert!(path.is_file());
475    ///
476    /// file.drop_async().await; // Explicitly drop the TempFile
477    ///
478    /// assert!(!path.exists());
479    /// # Ok::<(), Error>(())
480    /// # });
481    /// ```
482    pub async fn drop_async(self) {
483        let TempFile { file, core } = self;
484        // Close the local read-write handle before attempting deletion.
485        drop(file);
486
487        // Only the sole owner removes the file asynchronously; otherwise the
488        // remaining references' `Drop` impls handle cleanup.
489        let Some(core) = Arc::into_inner(core) else {
490            return;
491        };
492
493        if core.ownership.is_owned() {
494            // `core` is still marked owned here. If this future is cancelled or
495            // panics at the await point, `core` is dropped and its synchronous
496            // `Drop` deletes the file. We disarm only after a confirmed removal.
497            match tokio::fs::remove_file(&core.path).await {
498                Ok(()) => core.ownership.set_borrowed(),
499                Err(e) if e.kind() == ErrorKind::NotFound => core.ownership.set_borrowed(),
500                // Leave armed: the synchronous `Drop` below retries the removal.
501                Err(_) => {}
502            }
503        }
504
505        drop(core);
506    }
507
508    /// Closes the file, deleting it when this is the last reference to an owned
509    /// file, and **returns the removal result** so the caller can observe a
510    /// deletion failure - unlike the implicit `Drop`, which has no way to report
511    /// one. This is the synchronous sibling of [`drop_async`](Self::drop_async);
512    /// prefer `drop_async` inside an async context to avoid blocking the runtime
513    /// on the `unlink` syscall.
514    ///
515    /// If other clones still reference the file, cleanup is left to them and
516    /// `Ok(())` is returned. On error the file is left in place and the error is
517    /// returned; no further automatic deletion is attempted (a synchronous retry
518    /// of the same failing syscall would be pointless), so the caller owns the
519    /// file and may inspect, retry, or remove it. This differs from
520    /// [`drop_async`](Self::drop_async), whose synchronous `Drop` backstop is a
521    /// genuinely different deletion mechanism after a possibly-cancelled async
522    /// removal.
523    ///
524    /// ## Example
525    ///
526    /// ```rust
527    /// # use async_tempfile::{TempFile, Error};
528    /// # let _ = tokio_test::block_on(async {
529    /// let file = TempFile::new().await?;
530    /// let path = file.file_path().to_path_buf();
531    ///
532    /// file.close()?; // Explicitly close, surfacing any deletion error.
533    ///
534    /// assert!(!path.exists());
535    /// # Ok::<(), Error>(())
536    /// # });
537    /// ```
538    pub fn close(self) -> std::io::Result<()> {
539        let TempFile { file, core } = self;
540        // Close the local read-write handle before attempting deletion (matters
541        // on platforms that lock open files, such as Windows).
542        drop(file);
543
544        // Only the sole owner removes the file; otherwise the remaining
545        // references' `Drop` impls handle cleanup.
546        let Some(core) = Arc::into_inner(core) else {
547            return Ok(());
548        };
549
550        if core.ownership.is_owned() {
551            match std::fs::remove_file(&core.path) {
552                Ok(()) => core.ownership.set_borrowed(),
553                Err(e) if e.kind() == ErrorKind::NotFound => core.ownership.set_borrowed(),
554                // Disarm and surface the error: leaving `core` armed would make
555                // the trailing `Drop` retry the identical syscall for nothing.
556                // The file is left in place for the caller to handle.
557                Err(e) => {
558                    core.ownership.set_borrowed();
559                    return Err(e);
560                }
561            }
562        }
563
564        Ok(())
565    }
566
567    /// Creates a file named `{prefix}{random}{suffix}` with an unpredictable,
568    /// collision-resistant random core, using an exclusive (`O_EXCL`) create and
569    /// retrying on the (astronomically unlikely) collision. Shared by `new_in`
570    /// and [`crate::TempFileBuilder`].
571    pub(crate) async fn create_with_affixes(
572        dir: &Path,
573        prefix: &str,
574        suffix: &str,
575    ) -> Result<Self, Error> {
576        if !crate::path_is_dir(dir).await {
577            return Err(Error::InvalidDirectory);
578        }
579        // Affixes are filename fragments, not paths: a separator would let the
580        // composed name escape `dir` (`../` traversal, or an absolute prefix
581        // replacing it via `Path::join`).
582        if !crate::affix_is_safe(prefix) || !crate::affix_is_safe(suffix) {
583            return Err(Error::InvalidAffix);
584        }
585        let mut last_err = None;
586        for _ in 0..MAX_NAME_ATTEMPTS {
587            let name = format!("{prefix}{}{suffix}", Self::random_core_name());
588            match Self::new_internal(dir.join(name), Ownership::Owned, CreateMode::Exclusive).await
589            {
590                Ok(file) => return Ok(file),
591                Err(Error::Io(e)) if e.kind() == ErrorKind::AlreadyExists => {
592                    last_err = Some(Error::Io(e));
593                }
594                Err(e) => return Err(e),
595            }
596        }
597        Err(last_err.unwrap_or(Error::InvalidFile))
598    }
599
600    /// Generates the unpredictable, collision-resistant random core of a name,
601    /// without any prefix or suffix.
602    fn random_core_name() -> String {
603        #[cfg(feature = "uuid")]
604        {
605            Uuid::new_v4().to_string()
606        }
607
608        #[cfg(not(feature = "uuid"))]
609        {
610            RandomName::new("").as_str().to_string()
611        }
612    }
613
614    async fn new_internal<P: Borrow<Path>>(
615        path: P,
616        ownership: Ownership,
617        mode: CreateMode,
618    ) -> Result<Self, Error> {
619        let path = path.borrow();
620
621        // A single read-write handle both creates (per `mode`) and serves the
622        // file. Keeping just this one handle alive holds the inode open for the
623        // lifetime of the (shared) core, so no separate keep-alive handle is
624        // needed - that only wasted a file descriptor per file.
625        let mut options = OpenOptions::new();
626        options.read(true).write(true);
627        match mode {
628            CreateMode::Exclusive => {
629                options.create_new(true);
630            }
631            CreateMode::CreateOrOpen => {
632                options.create(true);
633            }
634            CreateMode::OpenExisting => {}
635        }
636
637        let file = options.open(path).await?;
638        let core = TempFileCore {
639            ownership: AtomicOwnership::new(ownership),
640            path: PathBuf::from(path),
641        };
642
643        Ok(Self {
644            file,
645            core: Arc::new(core),
646        })
647    }
648
649    /// Gets the default temporary file directory.
650    #[inline(always)]
651    fn default_dir() -> PathBuf {
652        std::env::temp_dir()
653    }
654}
655
656/// Ensures that the underlying file is deleted if this is an owned instance.
657/// If the underlying file is not owned, this operation does nothing.
658impl Drop for TempFileCore {
659    fn drop(&mut self) {
660        // Ensure we don't drop borrowed files. Read via the lock-free atomic:
661        // `Drop` may run on a runtime worker thread and must never block.
662        if !self.ownership.is_owned() {
663            return;
664        }
665
666        // The owning `TempFile`'s handle (declared before `core`) has already
667        // been closed by the time this runs, so the file can be deleted even on
668        // platforms that lock open files (Windows).
669        //
670        // Synchronous on purpose: `Drop` must not re-enter the async runtime, as
671        // it may run on a runtime worker thread. Use `drop_async` for an async
672        // deletion path at an explicit await point.
673        let _ = std::fs::remove_file(&self.path);
674    }
675}
676
677impl Debug for TempFileCore {
678    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
679        write!(f, "{:?}", self.path)
680    }
681}
682
683impl Debug for TempFile {
684    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
685        write!(f, "{:?}", self.core)
686    }
687}
688
689/// Allows implicit treatment of TempFile as a File.
690impl Deref for TempFile {
691    type Target = File;
692
693    fn deref(&self) -> &Self::Target {
694        &self.file
695    }
696}
697
698/// Allows implicit treatment of TempFile as a mutable File.
699impl DerefMut for TempFile {
700    fn deref_mut(&mut self) -> &mut File {
701        &mut self.file
702    }
703}
704
705impl Borrow<File> for TempFile {
706    fn borrow(&self) -> &File {
707        &self.file
708    }
709}
710
711impl BorrowMut<File> for TempFile {
712    fn borrow_mut(&mut self) -> &mut File {
713        &mut self.file
714    }
715}
716
717impl AsRef<File> for TempFile {
718    fn as_ref(&self) -> &File {
719        &self.file
720    }
721}
722
723/// Forwarding AsyncWrite to the embedded File
724impl AsyncWrite for TempFile {
725    fn poll_write(
726        mut self: Pin<&mut Self>,
727        cx: &mut Context<'_>,
728        buf: &[u8],
729    ) -> Poll<Result<usize, std::io::Error>> {
730        Pin::new(&mut self.file).poll_write(cx, buf)
731    }
732
733    fn poll_flush(
734        mut self: Pin<&mut Self>,
735        cx: &mut Context<'_>,
736    ) -> Poll<Result<(), std::io::Error>> {
737        Pin::new(&mut self.file).poll_flush(cx)
738    }
739
740    fn poll_shutdown(
741        mut self: Pin<&mut Self>,
742        cx: &mut Context<'_>,
743    ) -> Poll<Result<(), std::io::Error>> {
744        Pin::new(&mut self.file).poll_shutdown(cx)
745    }
746
747    fn poll_write_vectored(
748        mut self: Pin<&mut Self>,
749        cx: &mut Context<'_>,
750        bufs: &[IoSlice<'_>],
751    ) -> Poll<Result<usize, std::io::Error>> {
752        Pin::new(&mut self.file).poll_write_vectored(cx, bufs)
753    }
754}
755
756/// Forwarding AsyncRead to the embedded File
757impl AsyncRead for TempFile {
758    fn poll_read(
759        mut self: Pin<&mut Self>,
760        cx: &mut Context<'_>,
761        buf: &mut ReadBuf<'_>,
762    ) -> Poll<std::io::Result<()>> {
763        Pin::new(&mut self.file).poll_read(cx, buf)
764    }
765}
766
767/// Forwarding AsyncSeek to the embedded File
768impl AsyncSeek for TempFile {
769    fn start_seek(mut self: Pin<&mut Self>, position: SeekFrom) -> std::io::Result<()> {
770        Pin::new(&mut self.file).start_seek(position)
771    }
772
773    fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<u64>> {
774        Pin::new(&mut self.file).poll_complete(cx)
775    }
776}
777
778#[cfg(test)]
779mod tests {
780    use super::*;
781    use crate::random_name::RandomName;
782
783    #[test]
784    fn test_random_name() {
785        let name = RandomName::new(FILE_PREFIX);
786        assert!(name.as_ref().starts_with(FILE_PREFIX))
787    }
788}