Skip to content

Commit

Permalink
Update documentation and tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
TheVeryDarkness committed Aug 30, 2024
1 parent 512ad23 commit 35fd6c0
Show file tree
Hide file tree
Showing 9 changed files with 248 additions and 34 deletions.
134 changes: 130 additions & 4 deletions src/array.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
//! Helper for initializing an array of `MaybeUninit<T>` elements.
use std::mem::MaybeUninit;
use std::{
array::from_fn,
mem::{forget, MaybeUninit},
};

pub(crate) struct InitializingArray<'a, T, const N: usize> {
/// Helper for initializing an array of `MaybeUninit<T>` elements.
///
/// Once dopped, initialized elements in the array are also dropped.
/// Call [std::mem::forget] to drop the array without dropping the elements.
///
/// Borrow from the underlying implementation of [std::array::from_fn].
///
/// # Safety
///
/// The caller must ensure that the array is not read from until it is fully initialized,
/// the array is not used after it is dropped without calling [std::mem::forget],
/// and the length should not exceed the capacity.
struct ArrayGuard<'a, T, const N: usize> {
array: &'a mut [MaybeUninit<T>; N],
len: usize,
}

impl<T, const N: usize> Drop for InitializingArray<'_, T, N> {
impl<T, const N: usize> Drop for ArrayGuard<'_, T, N> {
fn drop(&mut self) {
for i in 0..self.len {
unsafe {
Expand All @@ -16,7 +31,8 @@ impl<T, const N: usize> Drop for InitializingArray<'_, T, N> {
}
}

impl<'a, T, const N: usize> InitializingArray<'a, T, N> {
impl<'a, T, const N: usize> ArrayGuard<'a, T, N> {
/// Create a new `InitializingArray` from a mutable reference to an array of `MaybeUninit<T>`.
pub(crate) fn new(array: &'a mut [MaybeUninit<T>; N]) -> Self {
Self { array, len: 0 }
}
Expand All @@ -28,3 +44,113 @@ impl<'a, T, const N: usize> InitializingArray<'a, T, N> {
self.len = self.len.wrapping_add(1);
}
}

pub(crate) fn array_from_fn<T, const N: usize>(mut f: impl FnMut() -> T) -> [T; N] {
let mut array: [MaybeUninit<T>; N] = from_fn(|_| MaybeUninit::uninit());
let mut guard = ArrayGuard::new(&mut array);
for _ in 0..N {
unsafe {
guard.push_unchecked(f());
}
}
forget(guard);
// Hope this is optimized well.
array.map(|x| unsafe { x.assume_init() })
}

pub(crate) fn array_try_from_fn<T, E, const N: usize>(
mut f: impl FnMut() -> Result<T, E>,
) -> Result<[T; N], E> {
let mut array: [MaybeUninit<T>; N] = from_fn(|_| MaybeUninit::uninit());
let mut guard = ArrayGuard::new(&mut array);
for _ in 0..N {
unsafe {
guard.push_unchecked(f()?);
}
}
forget(guard);
// Hope this is optimized well.
Ok(array.map(|x| unsafe { x.assume_init() }))
}

#[cfg(test)]
mod tracked {
use std::sync::atomic::{AtomicUsize, Ordering};

static COUNT: AtomicUsize = AtomicUsize::new(0);
pub struct Tracked(Box<usize>);
impl Tracked {
pub(super) fn new() -> Self {
loop {
let n = COUNT.load(Ordering::Relaxed);
if COUNT
.compare_exchange(n, n + 1, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
// Well, we shouldn't use println! without a lock in real code.
println!("Creating Tracked({})", n);
return Tracked(Box::new(n));
}
}
}
}
impl Drop for Tracked {
fn drop(&mut self) {
loop {
let n = COUNT.load(Ordering::Relaxed);
assert!(n > 0);
if COUNT
.compare_exchange(n, n - 1, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
break;
}
}
// Well, we shouldn't use println! without a lock in real code.
println!("Dropping Tracked({})", self.0);
}
}

/// Ensure all elements are dropped.
pub(super) fn check() {
assert_eq!(COUNT.load(Ordering::Relaxed), 0);
}
}

#[cfg(test)]
mod tests {
use super::{
array_from_fn,
tracked::{check, Tracked},
};
use std::panic::catch_unwind;

#[test]
fn array_string() {
let mut i = 0;
let array: [String; 3] = array_from_fn(|| {
i += 1;
i.to_string()
});
assert_eq!(array[0], "1");
assert_eq!(array[1], "2");
assert_eq!(array[2], "3");
}

#[test]
fn array_tracked_caught_panic() {
let res = catch_unwind(|| {
let mut i = 0;
let array: [Tracked; 3] = array_from_fn(|| {
if i >= 2 {
panic!("Sorry, something is wrong with the array.");
}
i += 1;
Tracked::new()
});
array
});
assert!(res.is_err());
check();
}
}
9 changes: 9 additions & 0 deletions src/formatted.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
use crate::sep_by;

/// [std::fmt::Display] with given separator.
///
/// # Examples
///
/// ```rust
/// use iof::SepBy;
/// let v = vec![1, 2, 3];
/// let s = format!("{}", v.sep_by(", "));
/// assert_eq!(s, "1, 2, 3");
/// ```
pub trait SepBy: IntoIterator {
/// Create an iterator that implement [core::fmt::Display] using given separator.
fn sep_by(self, sep: &'_ str) -> sep_by::SepBy<'_, Self::IntoIter>;
Expand Down
5 changes: 3 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
clippy::cargo
)]
#![forbid(clippy::should_panic_without_expect, clippy::incompatible_msrv)]
//! A utility library for reading integers, floating numbers and strings from input/output.
//! A utility library for reading data from input
//! and writting data from output.
pub use {
formatted::SepBy,
mat::Mat,
read_into::{ReadInto, ReadIntoError, ReadIntoSingle},
read_into::{parse::Parse, ReadInto, ReadIntoError, ReadIntoSingle},
stdio::read_into::*,
stream::InputStream,
};
Expand Down
11 changes: 10 additions & 1 deletion src/mat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,16 @@ use std::{fmt::Debug, iter::repeat_with, ops::Index};
mod row_iter;
mod tests;

/// A matrix.
/// A matrix with `m` rows and `n` columns.
///
/// # Examples
///
/// ```rust
/// use iof::Mat;
/// let mat = Mat::from_vec(2, 3, vec![1, 2, 3, 4, 5, 6]);
/// assert_eq!(mat[0], [1, 2, 3]);
/// assert_eq!(mat[1], [4, 5, 6]);
/// ```
#[derive(Clone, Default, PartialEq, Eq)]
pub struct Mat<T> {
inner: Vec<T>,
Expand Down
57 changes: 45 additions & 12 deletions src/read_into.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
use crate::{
array::InitializingArray,
array::{array_from_fn, array_try_from_fn},
mat::Mat,
stream::{InputStream, RealAll},
};
use parse::Parse;
use std::{
array::from_fn,
fmt::{Debug, Display},
io::BufRead,
mem::{forget, MaybeUninit},
};

pub(super) mod parse;

/// Error during using [ReadInto].
/// Error during using [ReadInto] or [ReadIntoSingle].
///
/// This error is usually caused by [std::io::Error] or [std::str::FromStr::Err].
pub enum ReadIntoError<E> {
Expand Down Expand Up @@ -56,6 +54,7 @@ macro_rules! unwrap {
}

/// Read a single data item from input stream.
/// All types that implement this trait also implement [ReadInto].
///
/// # Errors
///
Expand Down Expand Up @@ -188,14 +187,10 @@ where
{
type Error = <Self as ReadInto<T>>::Error;
fn try_read(&mut self) -> Result<[T; N], Self::Error> {
let mut array: [MaybeUninit<T>; N] = from_fn(|_| MaybeUninit::uninit());
let mut guard = InitializingArray::new(&mut array);
for _ in 0..N {
unsafe { guard.push_unchecked(self.try_read()?) }
}
forget(guard);
let array = array.map(|x| unsafe { x.assume_init() });
Ok(array)
array_try_from_fn(|| self.try_read())
}
fn read(&mut self) -> [T; N] {
array_from_fn(|| self.read())
}
}

Expand All @@ -211,6 +206,44 @@ where
}
}

// *These two implementations are not necessary*.
//
// impl<T, B: BufRead> ReadInto<Vec<T>> for InputStream<B>
// where
// Self: ReadInto<T> + ReadInto<usize>,
// {
// type Error = Tuple2Error<<Self as ReadInto<usize>>::Error, <Self as ReadInto<T>>::Error>;
// fn try_read(&mut self) -> Result<Vec<T>, Self::Error> {
// let len = self.try_read().map_err(Tuple2Error::T1)?;
// Ok(self.try_read_n(len).map_err(Tuple2Error::T2)?)
// }
// // Avoid constructing an enum value.
// fn read(&mut self) -> Vec<T> {
// let len = self.read();
// self.read_n(len)
// }
// }
//
// impl<T, B: BufRead> ReadInto<Mat<T>> for InputStream<B>
// where
// Self: ReadInto<T> + ReadInto<usize>,
// {
// // This is a bit tricky, as `m` and `n` are both `usize`.
// // We need to read `m` and `n` first, then read `m * n` elements.
// type Error = Tuple2Error<<Self as ReadInto<usize>>::Error, <Self as ReadInto<T>>::Error>;
// fn try_read(&mut self) -> Result<Mat<T>, Self::Error> {
// let m = self.try_read().map_err(Tuple2Error::T1)?;
// let n = self.try_read().map_err(Tuple2Error::T1)?;
// Ok(self.try_read_m_n(m, n).map_err(Tuple2Error::T2)?)
// }
// // Avoid constructing an enum value.
// fn read(&mut self) -> Mat<T> {
// let m = self.read();
// let n = self.read();
// self.read_m_n(m, n)
// }
// }

macro_rules! impl_read_into_for_tuple {
($e:ident $($t:ident)*) => {
#[derive(Debug)]
Expand Down
10 changes: 8 additions & 2 deletions src/read_into/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@ use std::{
str::FromStr,
};

/// Parse a string into a type.
/// Parse a string into a type. Similar to [std::str::FromStr], but we need this to avoid conflicting.
///
/// Similar to [std::str::FromStr].
/// # Example
///
/// ```rust
/// use iof::Parse;
/// let x: i32 = Parse::parse("42").unwrap();
/// assert_eq!(x, 42);
/// ```
pub trait Parse: Sized {
/// Error that comes from [Parse].
type Err: std::error::Error;
Expand Down
12 changes: 6 additions & 6 deletions src/stdio/read_into.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ macro_rules! expose_stdin {
///
/// # Panics
///
/// If [
/// If [`
#[doc = $str_try_fn]
/// ] panics.
/// `] panics.
///
/// # Errors
///
/// If this function is called in multiple threads, the behavior is undefined, possibly causing a deadlock.
///
/// If [
/// If [`
#[doc = $str_try_fn]
/// ] returns an error.
/// `] returns an error.
pub fn $try_fn<$($ty_arg)*>($($arg: $arg_ty),*) -> Result<$ret, $err>
where
InputStream<StdinLock<'static>>: ReadInto<T>,
Expand All @@ -38,9 +38,9 @@ macro_rules! expose_stdin {
///
/// # Panics
///
/// If [
/// If [`
#[doc = $str_try_fn]
/// ] returns an error or panics.
/// `] returns an error or panics.
///
/// # Errors
///
Expand Down
Loading

0 comments on commit 35fd6c0

Please sign in to comment.