1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
//! toad-async

// docs
#![doc(html_root_url = "https://docs.rs/toad-async/0.0.0")]
#![cfg_attr(any(docsrs, feature = "docs"), feature(doc_cfg))]
// -
// style
#![allow(clippy::unused_unit)]
// -
// deny
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![deny(missing_copy_implementations)]
#![cfg_attr(not(test), deny(unsafe_code))]
// -
// warnings
#![cfg_attr(not(test), warn(unreachable_pub))]
// -
// features
#![cfg_attr(not(feature = "std"), no_std)]

#[cfg(feature = "alloc")]
extern crate alloc as std_alloc;

/// Crate glob-reexport for API compatibility with the `nb` crate
pub mod nb {
  pub use crate::{Error, Result};
}

/// Non-blocking `Result`
pub type Result<T, E> = core::result::Result<T, Error<E>>;

/// Either an error occurred or the operation would block
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Error<E> {
  /// The operation would block
  WouldBlock,
  /// An error occurred
  Other(E),
}

impl<E> Error<E> {
  /// Map the error in [`Error::Other`]
  pub fn map<F, R>(self, mut f: F) -> Error<R>
    where F: FnMut(E) -> R
  {
    match self {
      | Error::Other(e) => Error::Other(f(e)),
      | Error::WouldBlock => Error::WouldBlock,
    }
  }
}

/// Poll the given function `f` until the result returned is not [`Error::WouldBlock`]
///
/// On `std` platforms, this will busy-wait with a small sleep (0.5 millis).
///
/// On `no-std` platforms, this will busy-wait.
pub fn block<T, E, F>(f: F) -> core::result::Result<T, E>
  where F: Fn() -> Result<T, E>
{
  loop {
    match f() {
      | Ok(ok) => break Ok(ok),
      | Err(Error::Other(e)) => break Err(e),
      | Err(Error::WouldBlock) => {
        #[cfg(feature = "std")]
        std::thread::sleep(std::time::Duration::from_nanos(500));
        continue;
      },
    }
  }
}

/// Helper methods for [`crate::Result`]
pub trait AsyncResult<T, E>
  where Self: Sized
{
  /// Map the error type contained in the result (if present)
  /// preserving `Ok` and `Err(Error::WouldBlock)`
  fn async_map_err<F, R>(self, f: F) -> Result<T, R>
    where F: FnMut(E) -> R;
}

impl<T, E> AsyncResult<T, E> for Result<T, E> {
  fn async_map_err<F, R>(self, mut f: F) -> Result<T, R>
    where F: FnMut(E) -> R
  {
    self.map_err(|e| match e {
          | Error::WouldBlock => Error::WouldBlock,
          | Error::Other(e) => Error::Other(f(e)),
        })
  }
}

/// Helper methods for nullary closures returning [`crate::Result`]
pub trait AsyncPollable<T, E> {
  /// Convenience method for [`crate::block`]
  ///
  /// ```no_run
  /// use std::fs::File;
  /// use std::io;
  ///
  /// use toad_async::{nb, AsyncPollable};
  ///
  /// fn wait_for_file() -> nb::Result<File, io::Error> {
  ///   match File::open("foo.txt") {
  ///     | Ok(f) => Ok(f),
  ///     | Err(e) if e.kind() == io::ErrorKind::NotFound => Err(nb::Error::WouldBlock),
  ///     | Err(e) => Err(nb::Error::Other(e)),
  ///   }
  /// }
  ///
  /// let f: io::Result<File> = wait_for_file.block();
  /// ```
  fn block(self) -> core::result::Result<T, E>;
}

impl<F, T, E> AsyncPollable<T, E> for F where F: Fn() -> Result<T, E>
{
  fn block(self) -> core::result::Result<T, E> {
    crate::block(self)
  }
}