maybe_fut/api/fs/
file.rs

1//! The main type for interacting with the file system is the [`File`] type.
2//! This type provides methods for reading and writing to files.
3
4use std::path::Path;
5
6use super::OpenOptions;
7use crate::{maybe_fut_constructor_result, maybe_fut_method};
8
9#[derive(Debug, Read, Seek, Write, Unwrap)]
10#[io(feature("tokio-fs"))]
11#[unwrap_types(std(std::fs::File), tokio(tokio::fs::File), tokio_gated("tokio-fs"))]
12/// A reference to an open file on the filesystem.
13pub struct File(FileInner);
14
15/// Inner pointer to sync or async file.
16#[derive(Debug)]
17enum FileInner {
18    /// Std variant of file <https://docs.rs/rustc-std-workspace-std/latest/std/fs/struct.File.html>
19    Std(std::fs::File),
20    #[cfg(tokio_fs)]
21    #[cfg_attr(docsrs, doc(cfg(feature = "tokio-fs")))]
22    /// Tokio variant of file <https://docs.rs/tokio/latest/tokio/fs/struct.File.html>
23    Tokio(tokio::fs::File),
24}
25
26impl From<std::fs::File> for File {
27    fn from(file: std::fs::File) -> Self {
28        Self(FileInner::Std(file))
29    }
30}
31
32#[cfg(tokio_fs)]
33#[cfg_attr(docsrs, doc(cfg(feature = "tokio-fs")))]
34impl From<tokio::fs::File> for File {
35    fn from(file: tokio::fs::File) -> Self {
36        Self(FileInner::Tokio(file))
37    }
38}
39
40impl File {
41    maybe_fut_constructor_result!(
42        /// Attempts to open a file in read-only mode.
43        /// See [`std::fs::OpenOptions`] for more details.
44        ///
45        /// # Errors
46        ///
47        /// This function will return an error if called from outside of the Tokio runtime (if async) or if path does not already exist.
48        /// Other errors may also be returned according to OpenOptions::open.
49        ///
50        /// See <https://docs.rs/rustc-std-workspace-std/latest/std/fs/struct.File.html#method.open>
51        open(path: impl AsRef<Path>) -> std::io::Result<Self>,
52        std::fs::File::open,
53        tokio::fs::File::open,
54        tokio_fs
55    );
56
57    maybe_fut_constructor_result!(
58        /// Attempts to open a file in read-only mode with buffering.
59        ///
60        /// # Errors
61        ///
62        /// This function will return an error if `path` does not already exist,
63        /// or if memory allocation fails for the new buffer.
64        /// Other errors may also be returned according to [`std::fs::OpenOptions::open`].
65        ///
66        /// See <https://docs.rs/rustc-std-workspace-std/latest/std/fs/struct.File.html#method.create>
67        create(path: impl AsRef<Path>) -> std::io::Result<Self>,
68        std::fs::File::create,
69        tokio::fs::File::create,
70        tokio_fs
71    );
72
73    maybe_fut_constructor_result!(
74        /// Opens a file in read-write mode.
75        ///
76        /// This function will create a file if it does not exist, or return an error
77        /// if it does. This way, if the call succeeds, the file returned is guaranteed
78        /// to be new.
79        ///
80        /// This option is useful because it is atomic. Otherwise between checking
81        /// whether a file exists and creating a new one, the file may have been
82        /// created by another process (a TOCTOU race condition / attack).
83        ///
84        /// This can also be written using `File::options().read(true).write(true).create_new(true).open(...)`.
85        ///
86        /// See [`std::fs::OpenOptions`] for more details.
87        /// See <https://docs.rs/rustc-std-workspace-std/latest/std/fs/struct.File.html#method.create_new>
88        create_new(path: impl AsRef<Path>) -> std::io::Result<Self>,
89        std::fs::File::create_new,
90        tokio::fs::File::create_new,
91        tokio_fs
92    );
93
94    maybe_fut_method!(
95        /// Queries metadata about the underlying file.
96        metadata() -> std::io::Result<std::fs::Metadata>,
97        FileInner::Std,
98        FileInner::Tokio,
99        tokio_fs
100    );
101
102    /// Returns a new [`OpenOptions`] object.
103    ///
104    /// This function returns a new OpenOptions object that you can use to open or create a file with specific options if open() or create() are not appropriate.
105    ///
106    /// It is equivalent to [`OpenOptions::new`], but allows you to write more readable code. Instead of `OpenOptions::new().append(true).open("example.log")`, you can write `File::options().append(true).open("example.log").await`.
107    /// This also avoids the need to import [`OpenOptions`].
108    ///
109    /// See the [`OpenOptions::new`] function for more details.
110    #[inline]
111    pub fn open_options() -> OpenOptions {
112        OpenOptions::new()
113    }
114
115    maybe_fut_method!(
116        /// Truncates or extends the underlying file, updating the size of this file to become size.
117        ///
118        /// If the size is less than the current file’s size, then the file will be shrunk.
119        /// If it is greater than the current file’s size, then the file will be extended to size and have all of the intermediate data filled in with 0s.
120        ///
121        /// # Errors
122        ///
123        /// This function will return an error if the file is not opened for writing.
124        set_len(size: u64) -> std::io::Result<()>,
125        FileInner::Std,
126        FileInner::Tokio,
127        tokio_fs
128    );
129
130    maybe_fut_method!(
131        /// Changes the permissions on the underlying file.
132        ///
133        /// Platform-specific behavior
134        /// This function currently corresponds to the fchmod function on Unix and the SetFileInformationByHandle function on Windows. Note that, this may change in the future.
135        ///
136        /// # Errors
137        ///
138        /// This function will return an error if the user lacks permission change attributes on the underlying file. It may also return an error in other os-specific unspecified cases.
139        set_permissions(perm: std::fs::Permissions) -> std::io::Result<()>,
140        FileInner::Std,
141        FileInner::Tokio,
142        tokio_fs
143    );
144
145    maybe_fut_method!(
146        /// Attempts to sync all OS-internal metadata to disk.
147        ///
148        /// This function will attempt to ensure that all in-core data reaches the filesystem before returning.
149        sync_all() -> std::io::Result<()>,
150        FileInner::Std,
151        FileInner::Tokio,
152        tokio_fs
153    );
154
155    maybe_fut_method!(
156        /// This function is similar to [`Self::sync_all`], except that it may not synchronize file metadata to the filesystem.
157        ///
158        /// This is intended for use cases that must synchronize content, but don’t need the metadata on disk.
159        /// The goal of this method is to reduce disk operations.
160        ///
161        /// Note that some platforms may simply implement this in terms of sync_all.
162        sync_data() -> std::io::Result<()>,
163        FileInner::Std,
164        FileInner::Tokio,
165        tokio_fs
166    );
167
168    /// Creates a new [`File`] instance that shares the same underlying file handle as the existing [`File`] instance.
169    /// Reads, writes, and seeks will affect both [`File`] instances simultaneously.
170    pub async fn try_clone(&self) -> std::io::Result<Self> {
171        match &self.0 {
172            FileInner::Std(file) => file.try_clone().map(Self::from),
173            #[cfg(tokio_fs)]
174            FileInner::Tokio(file) => file.try_clone().await.map(Self::from),
175        }
176    }
177    /// Converts the [`File`] inner instance to a [`std::fs::File`] instance if it is currently a [`tokio::fs::File`].
178    ///
179    /// This can be useful when you need for instance to pass an `impl std::io::Write` to a function.
180    pub async fn to_std(self) -> std::fs::File {
181        match self.0 {
182            FileInner::Std(file) => file,
183            #[cfg(tokio_fs)]
184            FileInner::Tokio(file) => file.into_std().await,
185        }
186    }
187
188    /// Converts the [`File`] inner instance to a [`tokio::fs::File`] instance if it is currently a [`std::fs::File`].
189    ///
190    /// This can be useful when you need for instance to pass an `impl tokio::io::AsyncWrite` to a function.
191    #[cfg(tokio_fs)]
192    #[cfg_attr(docsrs, doc(cfg(feature = "tokio-fs")))]
193    pub async fn to_tokio(self) -> tokio::fs::File {
194        match self.0 {
195            FileInner::Std(file) => tokio::fs::File::from_std(file),
196            FileInner::Tokio(file) => file,
197        }
198    }
199}
200
201#[cfg(unix)]
202impl std::os::fd::AsFd for File {
203    fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> {
204        match &self.0 {
205            FileInner::Std(file) => file.as_fd(),
206            #[cfg(tokio_fs)]
207            FileInner::Tokio(file) => file.as_fd(),
208        }
209    }
210}
211
212#[cfg(windows)]
213impl std::os::windows::io::AsHandle for File {
214    fn as_handle(&self) -> std::os::windows::io::BorrowedHandle<'_> {
215        match &self.0 {
216            FileInner::Std(file) => file.as_handle(),
217            #[cfg(tokio_fs)]
218            FileInner::Tokio(file) => file.as_handle(),
219        }
220    }
221}
222
223#[cfg(unix)]
224impl std::os::fd::AsRawFd for File {
225    fn as_raw_fd(&self) -> std::os::fd::RawFd {
226        match &self.0 {
227            FileInner::Std(file) => file.as_raw_fd(),
228            #[cfg(tokio_fs)]
229            FileInner::Tokio(file) => file.as_raw_fd(),
230        }
231    }
232}
233
234#[cfg(windows)]
235impl std::os::windows::io::AsRawHandle for File {
236    fn as_raw_handle(&self) -> std::os::windows::io::RawHandle {
237        match &self.0 {
238            FileInner::Std(file) => file.as_raw_handle(),
239            #[cfg(tokio_fs)]
240            FileInner::Tokio(file) => file.as_raw_handle(),
241        }
242    }
243}
244
245#[cfg(unix)]
246impl std::os::fd::FromRawFd for File {
247    unsafe fn from_raw_fd(fd: std::os::fd::RawFd) -> Self {
248        #[cfg(tokio_fs)]
249        {
250            if crate::context::is_async_context() {
251                Self(FileInner::Tokio(unsafe {
252                    tokio::fs::File::from_raw_fd(fd)
253                }))
254            } else {
255                Self(FileInner::Std(unsafe { std::fs::File::from_raw_fd(fd) }))
256            }
257        }
258        #[cfg(not(tokio_fs))]
259        {
260            Self(FileInner::Std(unsafe { std::fs::File::from_raw_fd(fd) }))
261        }
262    }
263}
264
265#[cfg(windows)]
266impl std::os::windows::io::FromRawHandle for File {
267    unsafe fn from_raw_handle(handle: std::os::windows::io::RawHandle) -> Self {
268        #[cfg(tokio_fs)]
269        {
270            if crate::context::is_async_context() {
271                Self(FileInner::Tokio(unsafe {
272                    tokio::fs::File::from_raw_handle(handle)
273                }))
274            } else {
275                Self(FileInner::Std(unsafe {
276                    std::fs::File::from_raw_handle(handle)
277                }))
278            }
279        }
280        #[cfg(not(tokio_fs))]
281        {
282            Self(FileInner::Std(unsafe {
283                std::fs::File::from_raw_handle(handle)
284            }))
285        }
286    }
287}
288
289#[cfg(test)]
290mod test {
291
292    use tempfile::NamedTempFile;
293
294    use super::*;
295    use crate::SyncRuntime;
296    use crate::io::{Read, Seek, Write};
297
298    #[test]
299    fn test_should_instantiate_file_sync() {
300        let temp = NamedTempFile::new().expect("Failed to create temp file");
301
302        // write file
303        std::fs::write(temp.path(), b"Hello world").expect("Failed to write file");
304
305        let variant = SyncRuntime::block_on(File::open(temp.path())).expect("Failed to open file");
306        assert!(matches!(variant.0, FileInner::Std(_)));
307    }
308
309    #[tokio::test]
310    async fn test_should_instantiate_file_async() {
311        let temp = NamedTempFile::new().expect("Failed to create temp file");
312
313        // write file
314        std::fs::write(temp.path(), b"Hello world").expect("Failed to write file");
315
316        let variant = File::open(temp.path()).await.expect("Failed to open file");
317        assert!(matches!(variant.0, FileInner::Tokio(_)));
318    }
319
320    #[test]
321    fn test_should_create_file_sync() {
322        let temp = NamedTempFile::new().expect("Failed to create temp file");
323
324        let variant =
325            SyncRuntime::block_on(File::create(temp.path())).expect("Failed to open file");
326        assert!(matches!(variant.0, FileInner::Std(_)));
327    }
328
329    #[tokio::test]
330    async fn test_should_create_file_async() {
331        let temp = NamedTempFile::new().expect("Failed to create temp file");
332
333        let variant = File::create(temp.path())
334            .await
335            .expect("Failed to open file");
336        assert!(matches!(variant.0, FileInner::Tokio(_)));
337    }
338
339    #[test]
340    fn test_should_get_metadata_sync() {
341        let temp = NamedTempFile::new().expect("Failed to create temp file");
342
343        // write file
344        std::fs::write(temp.path(), b"Hello world").expect("Failed to write file");
345
346        let file = SyncRuntime::block_on(File::open(temp.path())).expect("Failed to open file");
347        SyncRuntime::block_on(file.metadata()).expect("Failed to get metadata");
348    }
349
350    #[tokio::test]
351    async fn test_should_get_metadata_async() {
352        let temp = NamedTempFile::new().expect("Failed to create temp file");
353
354        // write file
355        std::fs::write(temp.path(), b"Hello world").expect("Failed to write file");
356
357        File::open(temp.path())
358            .await
359            .expect("Failed to open file")
360            .metadata()
361            .await
362            .expect("Failed to get metadata");
363    }
364
365    #[test]
366    fn test_should_convert_to_std() {
367        let temp = NamedTempFile::new().expect("Failed to create temp file");
368
369        // write file
370        std::fs::write(temp.path(), b"Hello world").expect("Failed to write file");
371
372        let file = SyncRuntime::block_on(File::open(temp.path())).expect("Failed to open file");
373        let _std_file = SyncRuntime::block_on(file.to_std());
374    }
375
376    #[tokio::test]
377    async fn test_should_convert_to_tokio() {
378        let temp = NamedTempFile::new().expect("Failed to create temp file");
379
380        // write file
381        std::fs::write(temp.path(), b"Hello world").expect("Failed to write file");
382
383        let file = File::open(temp.path()).await.expect("Failed to open file");
384        let _tokio_file = file.to_tokio().await;
385    }
386
387    #[test]
388    fn test_should_convert_to_std_sync() {
389        let temp = NamedTempFile::new().expect("Failed to create temp file");
390
391        // write file
392        std::fs::write(temp.path(), b"Hello world").expect("Failed to write file");
393
394        let file = SyncRuntime::block_on(File::open(temp.path())).expect("Failed to open file");
395        let _std_file = SyncRuntime::block_on(file.to_tokio());
396    }
397
398    #[tokio::test]
399    async fn test_should_convert_to_tokio_async() {
400        let temp = NamedTempFile::new().expect("Failed to create temp file");
401
402        // write file
403        std::fs::write(temp.path(), b"Hello world").expect("Failed to write file");
404
405        let file = File::open(temp.path()).await.expect("Failed to open file");
406        let _tokio_file = file.to_tokio().await;
407    }
408
409    #[test]
410    fn test_should_read_sync() {
411        let temp = NamedTempFile::new().expect("Failed to create temp file");
412
413        // write file
414        std::fs::write(temp.path(), b"Hello world").expect("Failed to write file");
415
416        let mut file = SyncRuntime::block_on(File::open(temp.path())).expect("Failed to open file");
417        let mut buf = vec![0; 11];
418        SyncRuntime::block_on(file.read(&mut buf)).expect("Failed to read file");
419        assert_eq!(buf, b"Hello world");
420    }
421
422    #[tokio::test]
423    async fn test_should_read_async() {
424        let temp = NamedTempFile::new().expect("Failed to create temp file");
425
426        // write file
427        std::fs::write(temp.path(), b"Hello world").expect("Failed to write file");
428
429        let mut file = File::open(temp.path()).await.expect("Failed to open file");
430        let mut buf = vec![0; 11];
431        file.read(&mut buf).await.expect("Failed to read file");
432        assert_eq!(buf, b"Hello world");
433    }
434
435    #[test]
436    fn test_should_write_sync() {
437        let temp = NamedTempFile::new().expect("Failed to create temp file");
438
439        let mut file =
440            SyncRuntime::block_on(File::create(temp.path())).expect("Failed to open file");
441        SyncRuntime::block_on(file.write(b"Hello world")).expect("Failed to write file");
442        SyncRuntime::block_on(file.flush()).expect("Failed to flush file");
443
444        let buf = std::fs::read(temp.path()).expect("Failed to read file");
445        assert_eq!(buf, b"Hello world");
446    }
447
448    #[tokio::test]
449    async fn test_should_write_async() {
450        let temp = NamedTempFile::new().expect("Failed to create temp file");
451
452        let mut file = File::create(temp.path())
453            .await
454            .expect("Failed to open file");
455        file.write(b"Hello world")
456            .await
457            .expect("Failed to write file");
458        file.flush().await.expect("Failed to flush file");
459
460        let buf = tokio::fs::read(temp.path())
461            .await
462            .expect("Failed to read file");
463        assert_eq!(buf, b"Hello world");
464    }
465
466    #[test]
467    fn test_should_seek_sync() {
468        let temp = NamedTempFile::new().expect("Failed to create temp file");
469
470        // write file
471        std::fs::write(temp.path(), b"Hello world").expect("Failed to write file");
472
473        let mut file = SyncRuntime::block_on(File::open(temp.path())).expect("Failed to open file");
474        let mut buf = vec![0; 5];
475        SyncRuntime::block_on(file.seek(std::io::SeekFrom::Start(6))).expect("Failed to seek file");
476        SyncRuntime::block_on(file.read(&mut buf)).expect("Failed to read file");
477        assert_eq!(buf, b"world");
478    }
479
480    #[tokio::test]
481    async fn test_should_seek_async() {
482        let temp = NamedTempFile::new().expect("Failed to create temp file");
483
484        // write file
485        std::fs::write(temp.path(), b"Hello world").expect("Failed to write file");
486
487        let mut file = File::open(temp.path()).await.expect("Failed to open file");
488        let mut buf = vec![0; 5];
489        file.seek(std::io::SeekFrom::Start(6))
490            .await
491            .expect("Failed to seek file");
492        file.read(&mut buf).await.expect("Failed to read file");
493        assert_eq!(buf, b"world");
494    }
495}