Skip to main content

ferroui_dynamic_data/
as_async_disposable.rs

1//! [`IAsyncDisposable`] and [`AsAsyncDisposable`]: the tests of upstream's `item is IDisposable`
2//! and `item is IAsyncDisposable`, which `AsyncDisposeMany` makes of the items it tracks.
3//!
4//! Deviation (DEVIATIONS.md, DynamicData): `IAsyncDisposable` belongs to the base library upstream;
5//! neither `ferroui-system` nor `ferroui-reactive` has it, so it lives here until `ferroui-system`
6//! does. Its `DisposeAsync` gives a `ValueTask`; here it gives a [`Task<()>`], the task type of
7//! `ferroui-system` (docs/porting/reactive.md, section 5.8).
8
9use ferroui_system::threading::tasks::Task;
10use ferroui_system::IDisposable;
11use std::rc::Rc;
12
13/// An object whose disposal ends later (`IAsyncDisposable`).
14pub trait IAsyncDisposable {
15    /// Starts the disposal and gives the task that ends with it; an error of the disposal faults
16    /// the task (`DisposeAsync`).
17    fn dispose_async(&self) -> Task<()>;
18}
19
20/// How an item is disposed: at once, or by a task (`item is IDisposable`, `item is
21/// IAsyncDisposable`).
22#[derive(Clone)]
23pub enum ItemDisposal {
24    /// The item is disposed at once (`IDisposable`).
25    Disposable(Rc<dyn IDisposable>),
26    /// The item is disposed by a task (`IAsyncDisposable`).
27    AsyncDisposable(Rc<dyn IAsyncDisposable>),
28}
29
30/// An item that may be disposable, at once or by a task: `AsyncDisposeMany` disposes the items for
31/// which [`to_item_disposal`](AsAsyncDisposable::to_item_disposal) gives a disposal, and leaves the
32/// others alone, as upstream disposes the items that implement `IDisposable` or
33/// `IAsyncDisposable`.
34///
35/// Deviation (DEVIATIONS.md, DynamicData): .NET tests the type of an item at run time, and prefers
36/// `IDisposable` when an item implements both. Rust has no such test for a generic item, so the
37/// type of the items says how they are disposed: an `Rc` of an [`IAsyncDisposable`] is disposed by
38/// its task, an `Rc<dyn IDisposable>` at once. Another type implements the trait, or
39/// `async_dispose_many_with` takes the function that tells how an item is disposed.
40pub trait AsAsyncDisposable {
41    /// How the item is disposed, or `None` when it is not disposable.
42    fn to_item_disposal(&self) -> Option<ItemDisposal>;
43}
44
45impl AsAsyncDisposable for Rc<dyn IDisposable> {
46    fn to_item_disposal(&self) -> Option<ItemDisposal> {
47        Some(ItemDisposal::Disposable(self.clone()))
48    }
49}
50
51impl AsAsyncDisposable for Rc<dyn IAsyncDisposable> {
52    fn to_item_disposal(&self) -> Option<ItemDisposal> {
53        Some(ItemDisposal::AsyncDisposable(self.clone()))
54    }
55}
56
57impl<X: IAsyncDisposable + 'static> AsAsyncDisposable for Rc<X> {
58    fn to_item_disposal(&self) -> Option<ItemDisposal> {
59        let disposable: Rc<dyn IAsyncDisposable> = self.clone();
60        Some(ItemDisposal::AsyncDisposable(disposable))
61    }
62}
63
64impl<X: AsAsyncDisposable> AsAsyncDisposable for Option<X> {
65    fn to_item_disposal(&self) -> Option<ItemDisposal> {
66        self.as_ref().and_then(AsAsyncDisposable::to_item_disposal)
67    }
68}