dir_structure/traits/
async_vfs.rs

1//! Asynchronous virtual file system traits.
2//!
3//! These traits are similar to the ones in the [`vfs`](super::vfs) module, but they use
4//! asynchronous I/O operations, and return `Future`s resolving to `Result`s instead of
5//! directly returning `Result`s, in line with Rust's async programming model.
6//!
7//! These traits require the `async` feature to be enabled.
8//!
9//! They are designed to be used with async runtimes like Tokio or async-std.
10//!
11//! You should refer to the documentation of the [`vfs`](super::vfs) module for
12//! more details on the individual methods, as the async versions have the same semantics,
13//! just with async I/O.
14//!
15//! An important difference between the [`VfsAsync`] traits and their synchronous counterparts
16//! is that the methods take _owned_ paths instead of references. This is because
17//! the async methods typically need to move the path into the future, and
18//! references would not be valid for the entire duration of the future.
19//!
20//! # Tool specific extensions of the async VFS traits
21//!
22//! Similarly to the synchronous VFS traits, there are also tool-specific extensions
23//! of the async VFS traits, which are required to use certain tools with a specific
24//! [`VfsAsync`] implementation.
25//!
26//! We list them here for convenience, but you should refer to the documentation of the
27//! individual tools for more details.
28//!
29//! ## [`AtomicDir<T>`](crate::atomic_dir::AtomicDir)
30//!
31//! To use the [`AtomicDir<T>`](crate::atomic_dir::AtomicDir) wrapper type with your async VFS implementation,
32//! the VFS type itself must implement the [`VfsSupportsTemporaryDirectories`](crate::atomic_dir::VfsSupportsTemporaryDirectories) trait.
33//! See its documentation for more details.
34//!
35//! ## [Images](crate::image)
36//!
37//! To allow reading and writing image files using the types and traits from the
38//! [`image`](crate::image) module, the following traits need to be implemented for your
39//! async VFS type `YourVfsType`:
40//!
41//! - `impl<T: ImgFormat> ReadImageFromAsync<T> for YourVfsType`                                  to satisfy the bound `T: ReadFromAsync<'vfs, YourVfsType>`
42//! - `impl<'a> WriteImageToAsync<'a> for YourVfsType`                                            to satisfy the bound `T: WriteToAsync<'a, YourVfsType>`
43//! - `impl<'a> WriteImageToAsyncRef<'a, YourVfsType> for YourVfsType`                            to satisfy the bound `T: WriteToAsyncRef<'a, YourVfsType>`
44//!
45//! These impls are required because the image encoding and decoding operations are CPU-bound and blocking,
46//! and thus cannot be implemented in a generic way for all async VFS implementations. They need to be implemented
47//! specifically for each async VFS type.
48//!
49//! You can see the implementations for the [`TokioFsVfs`](crate::vfs::tokio_fs_vfs::TokioFsVfs) VFS, which use
50//! [`tokio::task::spawn_blocking`] to offload the blocking operations to a separate thread pool.
51//!
52//! Your async VFS implementation might use a different async runtime, and thus might need to use a
53//! different method to offload blocking operations, but that is the ideal approach to take.
54//!
55//! You can of course not implement these impls, but then you will not be able to use the image
56//! encoding and decoding operations with your async VFS implementation.
57
58use std::io;
59use std::pin::Pin;
60use std::task::Context;
61use std::task::Poll;
62
63use futures::AsyncWrite;
64use futures::Stream;
65use futures::io::AsyncRead;
66use futures::io::AsyncSeek;
67use pin_project::pin_project;
68
69use crate::error::Error;
70use crate::error::Result;
71use crate::error::VfsResult;
72#[cfg(feature = "image")]
73use crate::image::ImgFormat;
74use crate::prelude::*;
75use crate::traits::vfs::DirEntryInfo;
76use crate::traits::vfs::OwnedPathType;
77use crate::traits::vfs::PathType;
78use crate::traits::vfs::VfsCore;
79
80/// An asynchronous virtual file system.
81///
82/// This is the asynchronous counterpart to the [`Vfs`] trait in the [`vfs`](super::vfs) module.
83///
84/// Writing operations are provided by the [`WriteSupportingVfsAsync` trait](self::WriteSupportingVfsAsync).
85pub trait VfsAsync: VfsCore + Send + Sync + Unpin {
86    /// The type of the file returned by the [`open_read` method](VfsAsync::open_read).
87    type RFile: AsyncRead + Send + Unpin;
88    /// The future returned by the [`open_read` method](VfsAsync::open_read).
89    type OpenReadFuture: Future<Output = VfsResult<Self::RFile, Self>> + Send + Unpin;
90
91    /// Opens a file for reading, at the specified path.
92    fn open_read(
93        self: Pin<&Self>,
94        path: <<Self as VfsCore>::Path as PathType>::OwnedPath,
95    ) -> Self::OpenReadFuture;
96
97    /// The future returned by the [`read` method](VfsAsync::read).
98    type ReadFuture<'a>: Future<Output = VfsResult<Vec<u8>, Self>> + Send + Unpin + 'a
99    where
100        Self: 'a;
101
102    /// Reads the contents of a file, at the specified path.
103    fn read<'a>(
104        self: Pin<&'a Self>,
105        path: <<Self as VfsCore>::Path as PathType>::OwnedPath,
106    ) -> Self::ReadFuture<'a>;
107
108    /// The future returned by the [`read_string` method](VfsAsync::read_string).
109    type ReadStringFuture<'a>: Future<Output = VfsResult<String, Self>> + Send + Unpin + 'a
110    where
111        Self: 'a;
112
113    /// Reads the contents of a file, at the specified path, and returns it as a string.
114    fn read_string<'a>(
115        self: Pin<&'a Self>,
116        path: <<Self as VfsCore>::Path as PathType>::OwnedPath,
117    ) -> Self::ReadStringFuture<'a>;
118
119    /// The future returned by the [`exists` method](VfsAsync::exists).
120    type ExistsFuture<'a>: Future<Output = VfsResult<bool, Self>> + Send + 'a
121    where
122        Self: 'a;
123
124    /// Checks if a file exists at the specified path.
125    fn exists<'a>(
126        self: Pin<&'a Self>,
127        path: <<Self as VfsCore>::Path as PathType>::OwnedPath,
128    ) -> Self::ExistsFuture<'a>;
129
130    /// The future type returned by the [`is_dir` method](VfsAsync::is_dir).
131    type IsDirFuture<'a>: Future<Output = VfsResult<bool, Self>> + Send + 'a
132    where
133        Self: 'a;
134
135    /// Checks if a directory exists at the specified path.
136    fn is_dir<'a>(
137        self: Pin<&'a Self>,
138        path: <<Self as VfsCore>::Path as PathType>::OwnedPath,
139    ) -> Self::IsDirFuture<'a>;
140
141    /// The stream type returned by the [`DirWalkFuture`](VfsAsync::DirWalkFuture).
142    type DirWalk<'a>: Stream<Item = VfsResult<DirEntryInfo<<Self as VfsCore>::Path>, Self>>
143        + Send
144        + 'a
145    where
146        Self: 'a;
147
148    /// The future type returned by the [`walk_dir` method](VfsAsync::walk_dir).
149    type DirWalkFuture<'a>: Future<Output = VfsResult<Self::DirWalk<'a>, Self>> + Send + 'a
150    where
151        Self: 'a;
152
153    /// Walks a directory at the given path, returning a stream of directory entries.
154    fn walk_dir<'a>(
155        self: Pin<&'a Self>,
156        path: <<Self as VfsCore>::Path as PathType>::OwnedPath,
157    ) -> Self::DirWalkFuture<'a>;
158}
159
160/// Marks that the [`RFile`](VfsAsync::RFile) type of this [`VfsAsync`] also implements
161/// [`AsyncSeek`], allowing it to be used in contexts that require seeking, such as image decoding.
162///
163/// This trait is automatically implemented for any [`VfsAsync`] whose [`RFile`](VfsAsync::RFile) implements
164/// [`AsyncSeek`].
165pub trait VfsAsyncWithSeekRead: VfsAsync
166where
167    Self::RFile: AsyncSeek + Send + Unpin,
168{
169}
170
171impl<T: VfsAsync> VfsAsyncWithSeekRead for T where T::RFile: AsyncSeek + Send + Unpin {}
172
173/// Extension trait for [`VfsAsync`] that provides additional convenience methods.
174pub trait VfsAsyncExt: VfsAsync {
175    /// Reads a file / directory at the specified path, and parses it into the specified type using its
176    /// [`ReadFromAsync`] implementation.
177    ///
178    /// This method takes `self` as a pinned reference, to ensure that the [`VfsAsync`] value
179    /// is not moved while the read operation is in progress.
180    fn read_typed_async_pinned<'a, T: ReadFromAsync<'a, Self>>(
181        self: Pin<&'a Self>,
182        path: impl Into<<<Self as VfsCore>::Path as PathType>::OwnedPath>,
183    ) -> T::Future {
184        T::read_from_async(path.into(), self)
185    }
186
187    /// Reads a file / directory at the specified path, and parses it into the specified type using its
188    /// [`ReadFromAsync`] implementation.
189    ///
190    /// This method takes `self` as a regular reference, and pins it internally.
191    fn read_typed_async<'a, T: ReadFromAsync<'a, Self>>(
192        &'a self,
193        path: impl Into<<<Self as VfsCore>::Path as PathType>::OwnedPath>,
194    ) -> T::Future {
195        Pin::new(self).read_typed_async_pinned::<T>(path)
196    }
197}
198
199// Blanket impl.
200impl<V: VfsAsync + ?Sized> VfsAsyncExt for V {}
201
202/// A virtual file system that supports writing operations.
203pub trait WriteSupportingVfsAsync: VfsAsync {
204    /// The type of the file returned by the [`open_write` method](WriteSupportingVfsAsync::open_write).
205    type WFile: AsyncWrite + Send + Unpin;
206
207    /// The future type returned by the [`open_write` method](WriteSupportingVfsAsync::open_write).
208    type OpenWriteFuture: Future<Output = VfsResult<Self::WFile, Self>> + Send + Unpin;
209
210    /// Opens a file for writing, at the specified path.
211    fn open_write(
212        self: Pin<&Self>,
213        path: <<Self as VfsCore>::Path as PathType>::OwnedPath,
214    ) -> Self::OpenWriteFuture;
215
216    /// The future type returned by the [`write` method](WriteSupportingVfsAsync::write).
217    type WriteFuture<'a>: Future<Output = VfsResult<(), Self>> + Send + Unpin + 'a
218    where
219        Self: 'a;
220
221    /// Writes the contents of a file, at the specified path.
222    fn write<'d, 'a: 'd>(
223        self: Pin<&'a Self>,
224        path: <<Self as VfsCore>::Path as PathType>::OwnedPath,
225        data: &'d [u8],
226    ) -> Self::WriteFuture<'d>;
227
228    /// The future type returned by the [`remove_dir_all` method](WriteSupportingVfsAsync::remove_dir_all).
229    type RemoveDirAllFuture<'a>: Future<Output = VfsResult<(), Self>> + Send + 'a
230    where
231        Self: 'a;
232
233    /// Removes a directory and all its contents.
234    fn remove_dir_all<'a>(
235        self: Pin<&'a Self>,
236        path: <<Self as VfsCore>::Path as PathType>::OwnedPath,
237    ) -> Self::RemoveDirAllFuture<'a>;
238
239    /// The future type returned by the [`create_dir` method](WriteSupportingVfsAsync::create_dir).
240    type CreateDirFuture<'a>: Future<Output = VfsResult<(), Self>> + Send + 'a
241    where
242        Self: 'a;
243    /// Creates a new directory at the specified path.
244    fn create_dir<'a>(
245        self: Pin<&'a Self>,
246        path: <<Self as VfsCore>::Path as PathType>::OwnedPath,
247    ) -> Self::CreateDirFuture<'a>;
248
249    /// The future type returned by the [`create_dir_all` method](WriteSupportingVfsAsync::create_dir_all).
250    type CreateDirAllFuture<'a>: Future<Output = VfsResult<(), Self>> + Send + 'a
251    where
252        Self: 'a;
253    /// Creates a new directory and all its parent directories at the specified path.
254    fn create_dir_all<'a>(
255        self: Pin<&'a Self>,
256        path: <<Self as VfsCore>::Path as PathType>::OwnedPath,
257    ) -> Self::CreateDirAllFuture<'a>;
258
259    /// The future type returned by the [`create_parent_dir` method](WriteSupportingVfsAsync::create_parent_dir).
260    type CreateParentDirFuture<'a>: Future<Output = VfsResult<(), Self>> + Send + 'a
261    where
262        Self: 'a;
263    /// Creates a new parent directory at the specified path.
264    fn create_parent_dir<'a>(
265        self: Pin<&'a Self>,
266        path: <<Self as VfsCore>::Path as PathType>::OwnedPath,
267    ) -> Self::CreateParentDirFuture<'a>;
268}
269
270/// Marks that the [`WFile`](WriteSupportingVfsAsync::WFile) type of this [`WriteSupportingVfsAsync`] also implements
271/// [`AsyncSeek`], allowing it to be used in
272/// contexts that require seeking.
273pub trait VfsAsyncWithSeekWrite: WriteSupportingVfsAsync
274where
275    Self::WFile: AsyncSeek + Send + Unpin,
276{
277}
278
279impl<T: WriteSupportingVfsAsync> VfsAsyncWithSeekWrite for T where T::WFile: AsyncSeek + Send + Unpin
280{}
281
282/// Extension trait for [`WriteSupportingVfsAsync`] that provides additional convenience methods.
283pub trait WriteSupportingVfsAsyncExt: WriteSupportingVfsAsync {
284    /// Writes a file / directory at the specified path, using the specified data type's
285    /// [`WriteToAsync`] implementation.
286    ///
287    /// This method takes `self` as a pinned reference, to ensure that the `VfsAsync` implementation
288    /// is not moved while the write operation is in progress.
289    fn write_typed_async_ref_pinned<'r, 'a: 'r, T: WriteToAsyncRef<'a, Self>>(
290        self: Pin<&'r Self>,
291        path: impl Into<<<Self as VfsCore>::Path as PathType>::OwnedPath>,
292        value: &'r T,
293    ) -> T::Future<'r> {
294        T::write_to_async_ref(value, path.into(), self)
295    }
296
297    /// Writes a file / directory at the specified path, using the specified data type's
298    /// [`WriteToAsync`] implementation.
299    ///
300    /// This method takes `self` as a regular reference, and pins it internally.
301    fn write_typed_async_ref<'r, 'a: 'r, T: WriteToAsyncRef<'a, Self>>(
302        &'r self,
303        path: impl Into<<<Self as VfsCore>::Path as PathType>::OwnedPath>,
304        data: &'r T,
305    ) -> T::Future<'r>
306    where
307        Self: Unpin,
308    {
309        Pin::new(self).write_typed_async_ref_pinned(path, data)
310    }
311
312    /// Writes a file / directory at the specified path, using the specified data type's
313    /// [`WriteToAsync`] implementation.
314    ///
315    /// This method takes `self` as a pinned reference, to ensure that the `VfsAsync` implementation
316    /// is not moved while the write operation is in progress.
317    fn write_typed_async_pinned<'a, T: WriteToAsync<'a, Self>>(
318        self: Pin<&'a Self>,
319        path: impl Into<<<Self as VfsCore>::Path as PathType>::OwnedPath>,
320        value: T,
321    ) -> T::Future {
322        value.write_to_async(path.into(), self)
323    }
324
325    /// Writes a file / directory at the specified path, using the specified data type's
326    /// [`WriteToAsync`] implementation.
327    ///
328    /// This method takes `self` as a regular reference, and pins it internally.
329    fn write_typed_async<'a, T: WriteToAsync<'a, Self>>(
330        &'a self,
331        path: impl Into<<<Self as VfsCore>::Path as PathType>::OwnedPath>,
332        value: T,
333    ) -> T::Future
334    where
335        Self: Unpin,
336    {
337        Pin::new(self).write_typed_async_pinned(path, value)
338    }
339}
340
341// Blanket impl.
342impl<V: WriteSupportingVfsAsync + ?Sized> WriteSupportingVfsAsyncExt for V {}
343
344#[pin_project(project_replace = CreateParentDirDefaultFutureProjOwn)]
345#[doc(hidden)]
346pub enum CreateParentDirDefaultFuture<'a, Vfs: WriteSupportingVfsAsync + 'a>
347where
348    for<'f> Vfs::ExistsFuture<'f>: Future<Output = VfsResult<bool, Vfs>> + Unpin,
349    for<'f> Vfs::CreateDirAllFuture<'f>: Future<Output = VfsResult<(), Vfs>> + Unpin,
350{
351    Poison,
352    Start {
353        vfs: Pin<&'a Vfs>,
354        path: <<Vfs as VfsCore>::Path as PathType>::OwnedPath,
355    },
356    ExistsFuture {
357        vfs: Pin<&'a Vfs>,
358        path: <<Vfs as VfsCore>::Path as PathType>::OwnedPath,
359        exists_future: Vfs::ExistsFuture<'a>,
360    },
361    CreateDirAllFuture {
362        vfs: Pin<&'a Vfs>,
363        create_dir_all_future: Vfs::CreateDirAllFuture<'a>,
364    },
365}
366
367impl<'a, Vfs: WriteSupportingVfsAsync + 'a> Future for CreateParentDirDefaultFuture<'a, Vfs>
368where
369    for<'f> Vfs::ExistsFuture<'f>: Future<Output = VfsResult<bool, Vfs>> + Unpin,
370    for<'f> Vfs::CreateDirAllFuture<'f>: Future<Output = VfsResult<(), Vfs>> + Unpin,
371{
372    type Output = VfsResult<(), Vfs>;
373
374    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
375        let this = self.as_mut().project_replace(Self::Poison);
376        match this {
377            CreateParentDirDefaultFutureProjOwn::Start { vfs, path } => {
378                self.project_replace(Self::ExistsFuture {
379                    exists_future: vfs.exists(path.clone()),
380                    vfs,
381                    path,
382                });
383                cx.waker().wake_by_ref();
384                Poll::Pending
385            }
386            CreateParentDirDefaultFutureProjOwn::ExistsFuture {
387                vfs,
388                path,
389                mut exists_future,
390            } => match Pin::new(&mut exists_future).poll(cx) {
391                Poll::Ready(Ok(true)) => Poll::Ready(Ok(())),
392                Poll::Ready(Ok(false)) => {
393                    self.project_replace(Self::CreateDirAllFuture {
394                        create_dir_all_future: vfs.create_dir_all(path),
395                        vfs,
396                    });
397                    cx.waker().wake_by_ref();
398                    Poll::Pending
399                }
400                Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
401                Poll::Pending => {
402                    self.project_replace(Self::ExistsFuture {
403                        vfs,
404                        path,
405                        exists_future,
406                    });
407                    Poll::Pending
408                }
409            },
410            CreateParentDirDefaultFutureProjOwn::CreateDirAllFuture {
411                vfs,
412                mut create_dir_all_future,
413            } => match Pin::new(&mut create_dir_all_future).poll(cx) {
414                Poll::Ready(Ok(())) => Poll::Ready(Ok(())),
415                Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
416                Poll::Pending => {
417                    self.project_replace(Self::CreateDirAllFuture {
418                        vfs,
419                        create_dir_all_future,
420                    });
421                    Poll::Pending
422                }
423            },
424            CreateParentDirDefaultFutureProjOwn::Poison => {
425                panic!("CreateParentDirDefaultFuture polled after completion")
426            }
427        }
428    }
429}
430
431#[pin_project(project = IoErrorWrapperFutureProj)]
432#[doc(hidden)]
433pub struct IoErrorWrapperFuture<T, F: Future<Output = io::Result<T>>, P: OwnedPathType> {
434    #[pin]
435    future: F,
436    path: P,
437}
438
439impl<T, F, P> IoErrorWrapperFuture<T, F, P>
440where
441    F: Future<Output = io::Result<T>>,
442    P: OwnedPathType,
443{
444    pub fn new(path: P, future: F) -> Self {
445        Self { future, path }
446    }
447}
448
449impl<T, F, P> Future for IoErrorWrapperFuture<T, F, P>
450where
451    F: Future<Output = io::Result<T>>,
452    P: OwnedPathType + Clone,
453{
454    type Output = Result<T, P>;
455
456    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
457        let this = self.as_mut().project();
458        match this.future.poll(cx) {
459            Poll::Ready(Ok(value)) => Poll::Ready(Ok(value)),
460            Poll::Ready(Err(e)) => {
461                let path = self.path.clone();
462                Poll::Ready(Err(Error::Io(path, e)))
463            }
464            Poll::Pending => Poll::Pending,
465        }
466    }
467}
468
469/// A trait implemented by async vfs implementations that support reading images.
470#[cfg(feature = "image")]
471#[cfg_attr(docsrs, doc(cfg(feature = "image")))]
472pub trait ReadImageFromAsync<T>: VfsAsync
473where
474    T: ImgFormat,
475{
476    /// The future type returned by the [`read_image_async` method](ReadImageFromAsync::read_image_async).
477    type ReadImageFuture<'a>: Future<Output = VfsResult<T, Self>> + Send + Unpin + 'a
478    where
479        Self: 'a;
480
481    /// Reads an image file at the specified path, returning the decoded image and its format.
482    fn read_image_async<'a>(
483        self: Pin<&'a Self>,
484        path: <Self::Path as PathType>::OwnedPath,
485    ) -> Self::ReadImageFuture<'a>;
486}
487
488#[cfg(feature = "image")]
489#[cfg_attr(docsrs, doc(cfg(feature = "image")))]
490impl<'vfs, Vfs, T> ReadFromAsync<'vfs, Vfs> for T
491where
492    Vfs: VfsAsync + ReadImageFromAsync<T> + 'vfs,
493    T: ImgFormat + Send + 'vfs,
494{
495    type Future = <Vfs as ReadImageFromAsync<T>>::ReadImageFuture<'vfs>;
496
497    fn read_from_async(
498        path: <Vfs::Path as PathType>::OwnedPath,
499        vfs: Pin<&'vfs Vfs>,
500    ) -> Self::Future {
501        Vfs::read_image_async(vfs, path)
502    }
503}
504
505/// A trait implemented by async vfs implementations that support writing images.
506#[cfg(feature = "image")]
507#[cfg_attr(docsrs, doc(cfg(feature = "image")))]
508pub trait WriteImageToAsync<'a>: WriteSupportingVfsAsync {
509    /// The future type returned by the [`write_image_async` method](WriteImageToAsync::write_image_async).
510    type WriteImageFuture: Future<Output = VfsResult<(), Self>> + Send + Unpin + 'a;
511
512    /// Writes an image file at the specified path, using the specified image and format.
513    fn write_image_async(
514        self: Pin<&'a Self>,
515        path: <Self::Path as PathType>::OwnedPath,
516        image: image::DynamicImage,
517        format: image::ImageFormat,
518    ) -> Self::WriteImageFuture;
519}
520
521// impl for owned images
522#[cfg(feature = "image")]
523#[cfg_attr(docsrs, doc(cfg(feature = "image")))]
524impl<'a, Vfs> WriteToAsync<'a, Vfs> for (image::DynamicImage, image::ImageFormat)
525where
526    Vfs: WriteSupportingVfsAsync + WriteImageToAsync<'a> + 'a,
527{
528    type Future = Vfs::WriteImageFuture;
529
530    fn write_to_async(
531        self,
532        path: <Vfs::Path as PathType>::OwnedPath,
533        vfs: Pin<&'a Vfs>,
534    ) -> Self::Future {
535        let (image, format) = self;
536        vfs.write_image_async(path, image, format)
537    }
538}
539
540/// A trait implemented by async vfs implementations that support writing images from references.
541#[cfg(feature = "image")]
542#[cfg_attr(docsrs, doc(cfg(feature = "image")))]
543pub trait WriteImageToAsyncRef<'a>: WriteSupportingVfsAsync {
544    /// The future type returned by the [`write_image_async_ref` method](WriteImageToAsyncRef::write_image_async_ref).
545    type WriteImageRefFuture: Future<Output = VfsResult<(), Self>> + Send + Unpin + 'a;
546
547    /// Writes an image file at the specified path, using the specified image reference and format.
548    fn write_image_async_ref(
549        self: Pin<&'a Self>,
550        path: <Self::Path as PathType>::OwnedPath,
551        image: &'a image::DynamicImage,
552        format: image::ImageFormat,
553    ) -> Self::WriteImageRefFuture;
554}
555
556// impl for image references
557#[cfg(feature = "image")]
558#[cfg_attr(docsrs, doc(cfg(feature = "image")))]
559impl<'a, Vfs: 'a> WriteToAsync<'a, Vfs> for (&'a image::DynamicImage, image::ImageFormat)
560where
561    Vfs: WriteSupportingVfsAsync + WriteImageToAsyncRef<'a>,
562{
563    type Future = Vfs::WriteImageRefFuture;
564
565    fn write_to_async(
566        self,
567        path: <Vfs::Path as PathType>::OwnedPath,
568        vfs: Pin<&'a Vfs>,
569    ) -> Self::Future {
570        let (image, format) = self;
571        vfs.write_image_async_ref(path, image, format)
572    }
573}