pub mod block {
pub trait Block<F>
where
F: std::future::IntoFuture,
{
fn block(self) -> F::Output;
}
impl<F> Block<F> for F
where
F: std::future::IntoFuture,
{
#[inline(always)]
fn block(self) -> F::Output {
crate::private::block(self.into_future())
}
}
#[macro_export]
macro_rules! async_block {
($($e:tt)*) => {
async {
$($e)*
}.block()
}
}
pub use async_block;
#[macro_export]
macro_rules! async_block_unwind {
($($e:tt)*) => {{
std::panic::catch_unwind(|| {
async {
$($e)*
}.block()
}).ok();
}}
}
pub use async_block_unwind;
#[macro_export]
macro_rules! async_closure_unwind {
($($e:tt)*) => {(
move || {
std::panic::catch_unwind(|| {
async {
$($e)*
}.block()
}).ok();
}
)}
}
pub use async_closure_unwind;
#[cfg(test)]
mod test_block {
use crate::prelude::*;
use std::time::{Duration, Instant};
async fn fetch_from_api() -> String {
std::thread::sleep(Duration::from_millis(100));
String::from("Api respone")
}
#[test]
fn should_block() {
assert_eq!("Api respone", fetch_from_api().block());
}
#[test]
fn async_closuer_unwind() {
std::thread::spawn(async_closure_unwind! {
assert_eq!("Api respone", fetch_from_api().await);
println!("ok");
panic!("error");
})
.join()
.unwrap();
println!("async closure prevented panic");
}
#[tokio::test(flavor = "multi_thread")]
async fn should_block_async() {
println!("Res: {}", fetch_from_api().block());
}
#[test]
fn should_block_async_block() {
let action = async {
let data1 = fetch_from_api().await;
let data2 = fetch_from_api().await;
format!("{data1}, {data2}")
};
let macro_result = async_block! {
let data1 = fetch_from_api().await;
let data2 = fetch_from_api().await;
format!("{data1}, {data2}")
};
assert_eq!("Api respone, Api respone", action.block());
assert_eq!("Api respone, Api respone", macro_result);
}
#[ignore = "only for manual testing"]
#[tokio::test(flavor = "multi_thread")]
async fn performance_test() {
async fn async_fn(i: i32) -> i32 {
10 + i
}
async_fn(0).await;
async_fn(0).block();
let now = Instant::now();
for i in 0..1_000_000 {
async_fn(i).await;
}
let async_took = now.elapsed().as_millis();
let now = Instant::now();
async_block! {
for i in 0..1_000_000 {
async_fn(i).await;
}
}
let async_block = now.elapsed().as_millis();
let now = Instant::now();
for i in 0..1_000_000 {
async_fn(i).block();
}
let async_block_in_loop = now.elapsed().as_millis();
println!("await took: {} ms", async_took);
println!("async_block took: {} ms", async_block);
println!("async_block in loop took: {} ms", async_block_in_loop);
}
}
}
pub mod unwind {
#[macro_export]
macro_rules! unwind {
($($e:tt)*) => {
std::panic::catch_unwind($($e)*)
}
}
pub use unwind;
#[cfg(test)]
mod test_unwind {
use super::*;
#[test]
fn test_unwind_closure_success() {
let x = String::from("test");
if let Some(y) = unwind!(|| x.to_owned() + "ok").ok() {
println!("result: y={y} x=moved")
} else {
println!("operation failed")
}
}
#[test]
fn test_unwind_closure_panic() {
let res = unwind!(|| panic!("error"));
assert!(res.is_err());
}
}
}
pub mod error {
pub trait ErrorToString<T> {
fn map_err_str(self) -> Result<T, String>;
}
impl<T, E: ToString> ErrorToString<T> for Result<T, E> {
fn map_err_str(self) -> Result<T, String> {
self.map_err(|e| e.to_string())
}
}
pub trait ErrorInto<T, E> {
fn err_into(self) -> Result<T, E>;
}
impl<EI, T, E: Into<EI>> ErrorInto<T, EI> for Result<T, E> {
fn err_into(self) -> Result<T, EI> {
match self {
Ok(ok) => Ok(ok),
Err(err) => Err(err.into()),
}
}
}
pub trait ExpectLazy<T> {
fn expect_lazy<F>(self, fun: F) -> T
where
F: Fn() -> String;
}
impl<T, E> ExpectLazy<T> for Result<T, E> {
fn expect_lazy<F>(self, fun: F) -> T
where
F: Fn() -> String,
{
match self {
Ok(ok) => ok,
Err(_) => panic!("{}", fun()),
}
}
}
impl<T> ExpectLazy<T> for Option<T> {
fn expect_lazy<F>(self, fun: F) -> T
where
F: Fn() -> String,
{
match self {
Some(ok) => ok,
None => panic!("{}", fun()),
}
}
}
#[cfg(test)]
mod test_error_into {
use super::ErrorInto;
#[derive(Debug, PartialEq, Eq)]
struct Error(pub String);
impl Into<Error> for String {
fn into(self) -> Error {
Error(self)
}
}
fn into_test() -> Result<String, String> {
Err("err".to_string())
}
fn test_into_call() -> Result<String, Error> {
into_test().err_into()
}
#[test]
fn testing() {
let res = test_into_call();
assert!(res.is_err());
assert_eq!(Error("err".into()), res.unwrap_err());
}
}
}
mod math {
pub trait Round<T> {
fn round_precision(self, digits: usize) -> T;
}
impl Round<f64> for f64 {
#[inline(always)]
fn round_precision(self, digits: usize) -> f64 {
let multi = 10.0_f64.powf(digits as f64);
(self * multi).round() / multi
}
}
impl Round<f32> for f32 {
#[inline(always)]
fn round_precision(self, digits: usize) -> f32 {
let multi = 10.0_f32.powf(digits as f32);
(self * multi).round() / multi
}
}
#[cfg(test)]
mod test_math {
use super::*;
#[test]
pub fn should_round() {
assert_eq!(0.0, 0.00300.round_precision(1));
assert_eq!(0.1, 0.10300.round_precision(1));
assert_eq!(0.0, 0.33333.round_precision(0));
assert_eq!(0.3, 0.33333.round_precision(1));
assert_eq!(0.33, 0.33333.round_precision(2));
assert_eq!(0.333, 0.33333.round_precision(3));
assert_eq!(0.334, 0.33355.round_precision(3));
assert_eq!(
0.02,
0.01499999999999999944488848768742172978818416595458984375_f64.round_precision(2)
);
assert_eq!(0.01, 0.014999999_f64.round_precision(2));
}
}
}
mod into {
pub trait IntoOption<T> {
fn none(self) -> Option<T>;
fn some(self) -> Option<T>;
}
impl<T> IntoOption<T> for T {
fn none(self) -> Option<T> {
None
}
fn some(self) -> Option<T> {
Some(self)
}
}
pub trait IntoResult<T> {
fn ok<E>(self) -> Result<T, E>;
fn err<S, IE>(self) -> Result<S, IE>
where
T: Into<IE>;
}
impl<T> IntoResult<T> for T {
fn err<S, IE>(self) -> Result<S, IE>
where
T: Into<IE>,
{
Err(self.into())
}
fn ok<E>(self) -> Result<T, E> {
Ok(self)
}
}
}
pub mod prelude {
pub use super::{
block::async_block, block::async_block_unwind, block::async_closure_unwind, block::Block,
error::ErrorInto, error::ErrorToString, error::ExpectLazy, into::IntoOption,
into::IntoResult, math::Round,
};
}