Skip to content

Commit

Permalink
implement IntoIterator for RArray
Browse files Browse the repository at this point in the history
Co-authored-by: Adam Petro <[email protected]>
  • Loading branch information
matsadler and adampetro committed Sep 8, 2023
1 parent cbcd301 commit 99bf18a
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 9 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
On creation the Array is hidden from Ruby, and must be consumed to pass it
to Ruby (where it reverts to a regular untyped Array). It is then
inaccessible to Rust.
- Implement `IntoIterator` for `RArray`.
- Implement `PartialEq`, `PartialOrd`, `Add`, `Sub`, `Mul`, and `Div` for
`Integer`.

Expand All @@ -21,6 +22,8 @@
argument of `&Ruby`.

### Deprecated
- `RArray::each`. Please use `ary.into_iter()` or
`ary.enumeratorize("each", ())` instead.

### Removed
- `deprecated-send-sync-value` feature.
Expand Down
8 changes: 2 additions & 6 deletions src/exception.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,8 @@ impl fmt::Debug for Exception {
unsafe {
writeln!(f, "{}: {}", self.classname(), self)?;
if let Ok(Some(backtrace)) = self.funcall::<_, _, Option<RArray>>("backtrace", ()) {
for line in backtrace.each() {
if let Ok(line) = line {
writeln!(f, "{}", line)?;
} else {
break;
}
for line in backtrace {
writeln!(f, "{}", line)?;
}
}
}
Expand Down
171 changes: 169 additions & 2 deletions src/r_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -438,8 +438,8 @@ impl RArray {
where
T: TryConvert,
{
for r in self.each() {
T::try_convert(r?)?;
for r in self {
T::try_convert(r)?;
}
unsafe {
let ary = rb_ary_hidden_new(0);
Expand Down Expand Up @@ -1205,11 +1205,16 @@ impl RArray {
/// # let _cleanup = unsafe { magnus::embed::init() };
///
/// let mut res = Vec::new();
/// # #[allow(deprecated)]
/// for i in eval::<RArray>("[1, 2, 3]").unwrap().each() {
/// res.push(i64::try_convert(i.unwrap()).unwrap());
/// }
/// assert_eq!(res, vec![1, 2, 3]);
/// ```
#[deprecated(
since = "0.7.0",
note = "Please use `ary.into_iter()` or `ary.enumeratorize(\"each\", ())` instead."
)]
pub fn each(self) -> Enumerator {
// TODO why doesn't rb_ary_each work?
self.enumeratorize("each", ())
Expand Down Expand Up @@ -1424,6 +1429,55 @@ impl fmt::Debug for RArray {
}
}

impl IntoIterator for RArray {
type Item = Value;
type IntoIter = Iter<Value>;

/// Returns an [`Iter`] over a copy of `self`.
///
/// `self` is copied using a fast copy-on-write optimisation, so if `self`
/// is not modified then `self` and the copy will point to the same backing
/// store and use no extra memory.
///
/// The copy is skipped if `self` is frozen.
///
/// # Examples
///
/// ```
/// use magnus::{Error, Ruby, TryConvert};
///
/// fn example(ruby: &Ruby) -> Result<(), Error> {
/// let ary = ruby.ary_new();
/// ary.push(1)?;
/// ary.push(2)?;
/// ary.push(3)?;
///
/// let iter = ary.into_iter();
///
/// ary.push(4)?;
///
/// let res = iter
/// .map(TryConvert::try_convert)
/// .collect::<Result<Vec<i64>, Error>>()?;
///
/// assert_eq!(res, vec![1, 2, 3]);
///
/// Ok(())
/// }
/// # Ruby::init(example).unwrap()
/// ```
fn into_iter(self) -> Self::IntoIter {
let ary = if self.is_frozen() {
self
} else {
let tmp = self.dup();
unsafe { rb_obj_hide(tmp.as_rb_value()) };
tmp
};
Iter::new(ary)
}
}

impl IntoValue for RArray {
#[inline]
fn into_value_with(self, _: &Ruby) -> Value {
Expand Down Expand Up @@ -1660,6 +1714,41 @@ where
unsafe { RArray::from_value_unchecked(self.0.get()).to_array() }
}

/// Returns an [`Iter`] over a copy of `self`.
///
/// `self` is copied using a fast copy-on-write optimisation, so if `self`
/// is not modified then `self` and the copy will point to the same backing
/// store and use no extra memory.
///
/// # Examples
///
/// ```
/// use magnus::{Error, Ruby};
///
/// fn example(ruby: &Ruby) -> Result<(), Error> {
/// let ary = ruby.typed_ary_new();
/// ary.push(ruby.integer_from_i64(1))?;
/// ary.push(ruby.integer_from_i64(2))?;
/// ary.push(ruby.integer_from_i64(3))?;
///
/// let iter = ary.iter();
///
/// ary.push(ruby.integer_from_i64(4))?;
///
/// let res = iter
/// .map(|int| int.to_usize())
/// .collect::<Result<Vec<_>, Error>>()?;
///
/// assert_eq!(res, vec![1, 2, 3]);
///
/// Ok(())
/// }
/// # Ruby::init(example).unwrap()
/// ```
pub fn iter(&self) -> Iter<T> {
Iter::new(unsafe { RArray::from_value_unchecked(self.dup().0.get()) })
}

// TODO? assoc & rassoc
}

Expand All @@ -1673,6 +1762,42 @@ where
}
}

impl<T> IntoIterator for TypedArray<T>
where
T: TryConvert,
{
type Item = T;
type IntoIter = Iter<T>;

/// Returns an [`Iter`] over `self`.
///
/// # Examples
///
/// ```
/// use magnus::{Error, Ruby};
///
/// fn example(ruby: &Ruby) -> Result<(), Error> {
/// let ary = ruby.typed_ary_new();
/// ary.push(ruby.integer_from_i64(1))?;
/// ary.push(ruby.integer_from_i64(2))?;
/// ary.push(ruby.integer_from_i64(3))?;
///
/// let res = ary
/// .into_iter()
/// .map(|int| int.to_usize())
/// .collect::<Result<Vec<_>, Error>>()?;
///
/// assert_eq!(res, vec![1, 2, 3]);
///
/// Ok(())
/// }
/// # Ruby::init(example).unwrap()
/// ```
fn into_iter(self) -> Self::IntoIter {
Iter::new(unsafe { RArray::from_value_unchecked(self.0.get()) })
}
}

impl<T> IntoValue for TypedArray<T>
where
T: IntoValue,
Expand All @@ -1688,3 +1813,45 @@ impl<T> gc::private::Mark for TypedArray<T> {
}
}
impl<T> gc::Mark for TypedArray<T> {}

/// An iterator over the elements of an array.
pub struct Iter<T> {
data: RArray,
len: usize,
idx: usize,
item_type: PhantomData<T>,
}

impl<T> Iterator for Iter<T>
where
T: TryConvert,
{
type Item = T;

#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.idx >= self.len {
None
} else {
let value = self.data.entry(self.idx as isize).ok();
self.idx += 1;
value
}
}

fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.len - self.idx;
(remaining, Some(remaining))
}
}

impl<T> Iter<T> {
fn new(data: RArray) -> Self {
Self {
data,
len: data.len(),
idx: 0,
item_type: PhantomData,
}
}
}
2 changes: 1 addition & 1 deletion tests/enumerator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use magnus::{prelude::*, RArray};
fn enumerator_impls_iterator() {
let ruby = unsafe { magnus::embed::init() };
let a: RArray = ruby.eval("[1,2,3]").unwrap();
let mut e = a.each();
let mut e = a.enumeratorize("each", ());
assert_eq!(e.next().unwrap().and_then(i64::try_convert).unwrap(), 1);
assert_eq!(e.next().unwrap().and_then(i64::try_convert).unwrap(), 2);
assert_eq!(e.next().unwrap().and_then(i64::try_convert).unwrap(), 3);
Expand Down

0 comments on commit 99bf18a

Please sign in to comment.