diff --git a/src/r_array.rs b/src/r_array.rs index 8e1a85f9..abf2bf94 100644 --- a/src/r_array.rs +++ b/src/r_array.rs @@ -264,14 +264,14 @@ impl Ruby { /// use magnus::{r_array::TypedFrozenRArray, Error, RString, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { - /// let typed_ary: TypedFrozenRArray = ruby.typed_frozen_r_array_empty(); + /// let typed_ary: TypedFrozenRArray = ruby.typed_frozen_ary_new(); /// assert!(typed_ary.is_empty()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` - pub fn typed_frozen_r_array_empty(&self) -> TypedFrozenRArray + pub fn typed_frozen_ary_new(&self) -> TypedFrozenRArray where T: TryConvert + Copy, { @@ -288,14 +288,14 @@ impl Ruby { /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { - /// let typed_ary = ruby.typed_frozen_r_array_from_iter((1..4).map(|i| i * 10)); + /// let typed_ary = ruby.typed_frozen_ary_from_iter((1..4).map(|i| i * 10)); /// rb_assert!(ruby, "ary == [10, 20, 30]", ary = typed_ary.as_value()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` - pub fn typed_frozen_r_array_from_iter(&self, iter: I) -> TypedFrozenRArray + pub fn typed_frozen_ary_from_iter(&self, iter: I) -> TypedFrozenRArray where I: IntoIterator, T: IntoValue, @@ -1264,7 +1264,12 @@ impl RArray { self.enumeratorize("each", ()) } - /// Returns an [`Iter`] over `self`. + /// Returns an [`Iter`] over `self`, without duplicating. Intended for cases where performance is critical. + /// Otherwise use `IntoIterator::into_iter`. + /// + /// # Safety + /// Mutating `self` while iterating over it will lead to undefined behavior, such as skipping items or iterating + /// an item multiple times. To avoid this, use `IntoIterator::into_iter` instead. /// /// # Examples /// @@ -1274,16 +1279,16 @@ impl RArray { /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_from_iter(1..4); /// - /// let res: Vec = ary.iter().map(TryConvert::try_convert).collect::, Error>>().unwrap(); + /// let res: Vec = unsafe { ary.iter() }.map(TryConvert::try_convert).collect::, Error>>().unwrap(); /// /// assert_eq!(res, vec![1, 2, 3]); /// - /// Ok(()) + /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` - pub fn iter(&self) -> Iter<'_, Value> { - Iter::new(self) + pub unsafe fn iter(&self) -> Iter { + Iter::new(*self) } /// Returns true if both `self` and `other` share the same backing storage. @@ -1483,6 +1488,45 @@ impl RArray { } } +impl IntoIterator for RArray { + type Item = Value; + type IntoIter = Iter; + + /// Returns an [`Iter`] over a copy of `self` (unless `self` is frozen), meaning that if `self` is mutated after + /// `into_iter` is called, that change will not be reflected in the iterator. + /// + /// # Examples + /// + /// ``` + /// use magnus::{Error, Ruby, TryConvert}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let ary = ruby.ary_from_iter(1..4); + /// + /// let iter = ary.into_iter(); + /// + /// ary.push(4)?; + /// + /// let res: Vec = iter.map(TryConvert::try_convert).collect::, Error>>().unwrap(); + /// + /// 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 dup = self.dup(); + dup.freeze(); + dup + }; + Iter::new(ary) + } +} + impl fmt::Display for RArray { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) @@ -1760,6 +1804,39 @@ impl gc::private::Mark for TypedArray { } impl gc::Mark for TypedArray {} +impl IntoIterator for TypedArray +where + T: TryConvert, +{ + type Item = T; + type IntoIter = Iter; + + /// Returns an [`Iter`] over `self`. + /// + /// # Examples + /// + /// ``` + /// use magnus::{Error, Ruby, TryConvert}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let ary = ruby.typed_ary_new::(); + /// ary.push(1)?; + /// ary.push(2)?; + /// ary.push(3)?; + /// + /// let res: Vec = ary.into_iter().collect(); + /// + /// assert_eq!(res, vec![1, 2, 3]); + /// + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() + /// ``` + fn into_iter(self) -> Self::IntoIter { + Iter::new(self.to_r_array()) + } +} + /// An iterator over the elements of an array. /// /// If the array is mutated during iteration, the iterator will not error, but @@ -1767,13 +1844,13 @@ impl gc::Mark for TypedArray {} /// the mutations made. /// /// See [`RArray::iter`] for details. -pub struct Iter<'a, T> { - data: &'a RArray, +pub struct Iter { + data: RArray, idx: usize, item_type: PhantomData, } -impl Iterator for Iter<'_, T> +impl Iterator for Iter where T: TryConvert, { @@ -1795,8 +1872,8 @@ where } } -impl<'a, T> Iter<'a, T> { - fn new(data: &'a RArray) -> Self { +impl Iter { + fn new(data: RArray) -> Self { Self { data, idx: 0, @@ -1814,49 +1891,6 @@ impl<'a, T> Iter<'a, T> { pub struct TypedFrozenRArray(RArray, PhantomData); impl TypedFrozenRArray { - /// Creates a new typed frozen array from the given Ruby array. Will freeze the array. - /// - /// # Errors - /// - /// If any of the elements are not of the specified type. - /// - /// # Examples - /// - /// ``` - /// use magnus::{rb_assert, r_array::TypedFrozenRArray, Error, Ruby}; - /// - /// fn example(ruby: &Ruby) -> Result<(), Error> { - /// let ary = ruby.ary_from_iter(1..4); - /// let typed_ary: TypedFrozenRArray = TypedFrozenRArray::new(ary)?; - /// rb_assert!(ruby, "ary == [1, 2, 3]", ary = typed_ary.as_value()); - /// - /// Ok(()) - /// } - /// # Ruby::init(example).unwrap() - /// ``` - /// - /// ``` - /// use magnus::{r_array::TypedFrozenRArray, Error, RString, Ruby}; - /// - /// fn example(ruby: &Ruby) -> Result<(), Error> { - /// let ary = ruby.ary_from_iter(1..4); - /// let error = TypedFrozenRArray::::new(ary).unwrap_err(); - /// assert_eq!(error.to_string(), "no implicit conversion of Integer into String"); - /// - /// Ok(()) - /// } - /// # Ruby::init(example).unwrap() - /// ``` - pub fn new(data: RArray) -> Result - where - T: TryConvert, - { - data.freeze(); - data.iter() - .try_for_each(|el| T::try_convert(el).map(|_| ()))?; - Ok(Self(data, PhantomData)) - } - /// Return the number of entries in `self` as a Rust [`usize`]. /// /// # Examples @@ -1865,7 +1899,7 @@ impl TypedFrozenRArray { /// use magnus::{r_array::TypedFrozenRArray, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { - /// let ary: TypedFrozenRArray = ruby.typed_frozen_r_array_from_iter(1..4); + /// let ary: TypedFrozenRArray = ruby.typed_frozen_ary_from_iter(1..4); /// assert_eq!(ary.len(), 3); /// /// Ok(()) @@ -1884,10 +1918,10 @@ impl TypedFrozenRArray { /// use magnus::{r_array::TypedFrozenRArray, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { - /// let ary: TypedFrozenRArray = ruby.typed_frozen_r_array_from_iter(1..4); + /// let ary: TypedFrozenRArray = ruby.typed_frozen_ary_from_iter(1..4); /// assert!(!ary.is_empty()); /// - /// let ary: TypedFrozenRArray = ruby.typed_frozen_r_array_empty(); + /// let ary: TypedFrozenRArray = ruby.typed_frozen_ary_new(); /// assert!(ary.is_empty()); /// /// Ok(()) @@ -1897,6 +1931,14 @@ impl TypedFrozenRArray { pub fn is_empty(&self) -> bool { self.0.is_empty() } +} + +impl IntoIterator for TypedFrozenRArray +where + T: TryConvert + Copy, +{ + type Item = T; + type IntoIter = Iter; /// Returns an [`Iter`] over `self`. /// @@ -1906,9 +1948,9 @@ impl TypedFrozenRArray { /// use magnus::{Error, r_array::TypedFrozenRArray, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { - /// let ary: TypedFrozenRArray = ruby.typed_frozen_r_array_from_iter(1..4); + /// let ary: TypedFrozenRArray = ruby.typed_frozen_ary_from_iter(1..4); /// - /// let res: Vec = ary.iter().collect(); + /// let res: Vec = ary.into_iter().collect(); /// /// assert_eq!(res, vec![1, 2, 3]); /// @@ -1916,8 +1958,8 @@ impl TypedFrozenRArray { /// } /// # Ruby::init(example).unwrap() /// ``` - pub fn iter(&self) -> Iter<'_, T> { - Iter::new(&self.0) + fn into_iter(self) -> Self::IntoIter { + Iter::new(self.0) } } @@ -1927,12 +1969,51 @@ unsafe impl private::ReprValue for TypedFrozenRArray where T: TryConvert + impl ReprValue for TypedFrozenRArray where T: TryConvert + Copy {} -impl TryConvert for TypedFrozenRArray +impl TryFrom for TypedFrozenRArray where T: TryConvert + Copy, { - fn try_convert(val: Value) -> Result { - RArray::try_convert(val).and_then(Self::new) + type Error = Error; + + /// Creates a new typed frozen array from the given Ruby array. Will freeze the array. + /// + /// # Errors + /// + /// If any of the elements are not of the specified type. + /// + /// # Examples + /// + /// ``` + /// use magnus::{rb_assert, r_array::TypedFrozenRArray, Error, Ruby}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let ary = ruby.ary_from_iter(1..4); + /// let typed_ary: TypedFrozenRArray = ary.try_into()?; + /// rb_assert!(ruby, "ary == [1, 2, 3]", ary = typed_ary.as_value()); + /// + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() + /// ``` + /// + /// ``` + /// use magnus::{r_array::TypedFrozenRArray, Error, RString, Ruby}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let ary = ruby.ary_from_iter(1..4); + /// let error = TypedFrozenRArray::::try_from(ary).unwrap_err(); + /// assert_eq!(error.to_string(), "no implicit conversion of Integer into String"); + /// + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() + /// ``` + fn try_from(value: RArray) -> Result { + value.freeze(); + value + .into_iter() + .try_for_each(|el| T::try_convert(el).map(|_| ()))?; + Ok(Self(value, PhantomData)) } }