diff --git a/rten-tensor/src/errors.rs b/rten-tensor/src/errors.rs index 6a195346..c12c69cb 100644 --- a/rten-tensor/src/errors.rs +++ b/rten-tensor/src/errors.rs @@ -3,6 +3,8 @@ use std::error::Error; use std::fmt::{Display, Formatter}; +use crate::slice_range::SliceRange; + /// Error in a tensor operation if the dimension count is incorrect. #[derive(Debug, PartialEq)] pub struct DimensionError {} @@ -47,28 +49,82 @@ impl Error for FromDataError {} #[derive(Clone, Debug, PartialEq)] pub enum SliceError { /// The slice spec has more dimensions than the tensor being sliced. - TooManyDims, + TooManyDims { + /// Number of axes in the tensor. + ndim: usize, + /// Number of items in the slice spec. + range_ndim: usize, + }, /// An index in the slice spec is out of bounds for the corresponding tensor /// dimension. - InvalidIndex, + InvalidIndex { + /// Axis that the error applies to. + axis: usize, + /// Index in the slice range. + index: isize, + /// Size of the dimension. + size: usize, + }, /// A range in the slice spec is out of bounds for the corresponding tensor /// dimension. - InvalidRange, + InvalidRange { + /// Axis that the error applies to. + axis: usize, + + /// The range item. + range: SliceRange, + + /// Size of the dimension. + size: usize, + }, /// The step in a slice range is negative, in a context where this is not /// supported. - InvalidStep, + InvalidStep { + /// Axis that the error applies to. + axis: usize, + + /// Size of the dimension. + step: isize, + }, + + /// There is a mismatch between the actual and expected number of axes + /// in the output slice. + OutputDimsMismatch { actual: usize, expected: usize }, } impl Display for SliceError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - SliceError::TooManyDims => write!(f, "slice spec has too many dims"), - SliceError::InvalidIndex => write!(f, "slice index is invalid"), - SliceError::InvalidRange => write!(f, "slice range is invalid"), - SliceError::InvalidStep => write!(f, "slice step is invalid"), + SliceError::TooManyDims { ndim, range_ndim } => { + write!( + f, + "slice range has {} items but tensor has only {} dims", + range_ndim, ndim + ) + } + SliceError::InvalidIndex { axis, index, size } => write!( + f, + "slice index {} is invalid for axis ({}) of size {}", + index, axis, size + ), + SliceError::InvalidRange { axis, range, size } => write!( + f, + "slice range {:?} is invalid for axis ({}) of size {}", + range, axis, size + ), + SliceError::InvalidStep { axis, step } => { + write!(f, "slice step {} is invalid for axis {}", step, axis) + } + SliceError::OutputDimsMismatch { actual, expected } => { + write!( + f, + "slice output dims {} does not match expected dims {}", + actual, expected + ) + } } } } diff --git a/rten-tensor/src/iterators.rs b/rten-tensor/src/iterators.rs index 7bb00e80..a4f8d819 100644 --- a/rten-tensor/src/iterators.rs +++ b/rten-tensor/src/iterators.rs @@ -470,7 +470,7 @@ impl LaneRanges { (0..end).into() }) .collect(); - let (_range, sliced) = layout.slice_dyn(&slice_starts); + let (_range, sliced) = layout.slice_dyn(&slice_starts).unwrap(); let offsets = Offsets::new(&sliced); LaneRanges { offsets, diff --git a/rten-tensor/src/layout.rs b/rten-tensor/src/layout.rs index 3600f680..ff112221 100644 --- a/rten-tensor/src/layout.rs +++ b/rten-tensor/src/layout.rs @@ -300,19 +300,29 @@ fn slice_layout, O: AsMut<[usize]>>( for (in_dim, (&size, &stride)) in in_shape.iter().zip(in_strides.iter()).enumerate() { let (offset_adjust, new_size_stride) = match range.get(in_dim) { Some(&SliceItem::Index(idx)) => { - let size = size as isize; - let pos_idx = if idx >= 0 { idx } else { idx + size }; - if pos_idx < 0 || pos_idx >= size { - return Err(SliceError::InvalidIndex); + let pos_idx = if idx >= 0 { idx } else { idx + size as isize }; + if pos_idx < 0 || pos_idx >= size as isize { + return Err(SliceError::InvalidIndex { + axis: in_dim, + index: idx, + size, + }); } (stride * pos_idx as usize, None) } Some(SliceItem::Range(range)) => { - let resolved = range.resolve(size).ok_or(SliceError::InvalidRange)?; + let resolved = range.resolve(size).ok_or(SliceError::InvalidRange { + axis: in_dim, + range: *range, + size, + })?; let step: usize = range .step() .try_into() - .map_err(|_| SliceError::InvalidStep)?; + .map_err(|_| SliceError::InvalidStep { + axis: in_dim, + step: range.step(), + })?; let new_size = if step == 1 { // Fast path when no custom step is used. resolved.end - resolved.start @@ -472,22 +482,32 @@ impl NdLayout { /// an existing tensor view. /// /// Returns a tuple of (offset_range, layout) for the sliced view. - pub fn slice(&self, range: &[SliceItem]) -> (Range, NdLayout) { - assert!( - self.ndim() >= range.len(), - "Slice dims must be <= current dims" - ); + pub fn slice( + &self, + range: &[SliceItem], + ) -> Result<(Range, NdLayout), SliceError> { + if self.ndim() < range.len() { + return Err(SliceError::TooManyDims { + ndim: self.ndim(), + range_ndim: range.len(), + }); + } let mut shape: [usize; M] = [0; M]; let mut strides: [usize; M] = [0; M]; let (ndim, offset) = - slice_layout(&self.shape, &self.strides, &mut shape, &mut strides, range).unwrap(); + slice_layout(&self.shape, &self.strides, &mut shape, &mut strides, range)?; - assert!(ndim == M, "sliced dims != {}", M); + if ndim != M { + return Err(SliceError::OutputDimsMismatch { + actual: ndim, + expected: M, + }); + } let layout = NdLayout { shape, strides }; - (offset..offset + layout.min_data_len(), layout) + Ok((offset..offset + layout.min_data_len(), layout)) } pub fn resize_dim(&mut self, dim: usize, new_size: usize) { @@ -684,33 +704,17 @@ impl DynLayout { self.shape_and_strides.insert(ndim + to, stride); } - /// Compute the new layout and offset of the first element for a slice into - /// an existing tensor view. - /// - /// Returns a tuple of (offset_range, layout) for the sliced view. - /// - /// Panics if the range is invalid for the current layout. - pub fn slice(&self, range: &[SliceItem]) -> (Range, DynLayout) { - match self.try_slice(range) { - Ok(result) => result, - - // These error conversions preserve existing error messages in - // various tests. - Err(SliceError::InvalidRange) => panic!("Slice range is invalid for tensor shape"), - Err(SliceError::InvalidIndex) => panic!("Slice index is invalid for tensor shape"), - Err(SliceError::InvalidStep) => panic!("Cannot slice with negative step"), - Err(err) => panic!("{:?}", err), - } - } - /// Compute the new layout and offset of the first element for a slice into /// an existing tensor view. /// /// Returns a tuple of (offset_range, layout) for the sliced view, or an /// error if the range is invalid. - pub fn try_slice(&self, range: &[SliceItem]) -> Result<(Range, DynLayout), SliceError> { + pub fn slice(&self, range: &[SliceItem]) -> Result<(Range, DynLayout), SliceError> { if self.ndim() < range.len() { - return Err(SliceError::TooManyDims); + return Err(SliceError::TooManyDims { + ndim: self.ndim(), + range_ndim: range.len(), + }); } let out_dims = self.ndim() @@ -930,10 +934,18 @@ pub trait MutLayout: Layout + Clone { /// `self.permuted([N-1, N-2, ... 0])`. fn transposed(&self) -> Self; - /// Slice the layout. + /// Slice the layout and return a static-rank layout. + /// + /// Returns a tuple of `(offset_range, sliced_layout)`. + fn slice( + &self, + range: &[SliceItem], + ) -> Result<(Range, NdLayout), SliceError>; + + /// Slice the layout and return a dynamic rank layout. /// /// Returns a tuple of `(offset_range, sliced_layout)`. - fn slice(&self, range: &[SliceItem]) -> (Range, NdLayout); + fn slice_dyn(&self, range: &[SliceItem]) -> Result<(Range, DynLayout), SliceError>; /// Slice the layout along a given axis. /// @@ -953,11 +965,6 @@ pub trait MutLayout: Layout + Clone { (range, sliced_layout) } - /// Slice the layout and return a dynamic rank layout. - /// - /// Returns a tuple of `(offset_range, sliced_layout)`. - fn slice_dyn(&self, range: &[SliceItem]) -> (Range, DynLayout); - /// Return a layout with all size-one dimensions removed. fn squeezed(&self) -> DynLayout; @@ -966,13 +973,6 @@ pub trait MutLayout: Layout + Clone { /// Returns a tuple of `(left, right)` where each item is an `(offset_range, /// layout)` tuple. fn split(&self, axis: usize, mid: usize) -> ((Range, Self), (Range, Self)); - - /// Attempt to slice the layout or return an error if the range is invalid - /// for the layout's shape. - fn try_slice( - &self, - range: R, - ) -> Result<(Range, DynLayout), SliceError>; } /// Trait for broadcasting a layout from one shape to another. @@ -1040,11 +1040,14 @@ impl MutLayout for NdLayout { self.transposed() } - fn slice(&self, range: &[SliceItem]) -> (Range, NdLayout) { + fn slice( + &self, + range: &[SliceItem], + ) -> Result<(Range, NdLayout), SliceError> { self.slice(range) } - fn slice_dyn(&self, range: &[SliceItem]) -> (Range, DynLayout) { + fn slice_dyn(&self, range: &[SliceItem]) -> Result<(Range, DynLayout), SliceError> { self.as_dyn().slice(range) } @@ -1086,14 +1089,6 @@ impl MutLayout for NdLayout { ((left_offsets, left), (right_offsets, right)) } - - fn try_slice( - &self, - range: R, - ) -> Result<(Range, DynLayout), SliceError> { - let items = range.into_slice_items(); - self.as_dyn().try_slice(items.as_ref()) - } } impl MutLayout for DynLayout { @@ -1125,19 +1120,20 @@ impl MutLayout for DynLayout { self.transposed() } - fn slice(&self, range: &[SliceItem]) -> (Range, NdLayout) { - let (offset_range, dyn_layout) = self.slice(range); - let nd_layout = NdLayout::try_from(&dyn_layout).unwrap_or_else(|_| { - panic!( - "expected sliced tensor to have {} dims but it has {}", - M, - dyn_layout.ndim() - ); - }); - (offset_range, nd_layout) - } - - fn slice_dyn(&self, range: &[SliceItem]) -> (Range, DynLayout) { + fn slice( + &self, + range: &[SliceItem], + ) -> Result<(Range, NdLayout), SliceError> { + let (offset_range, dyn_layout) = self.slice(range)?; + let nd_layout = + NdLayout::try_from(&dyn_layout).map_err(|_| SliceError::OutputDimsMismatch { + actual: dyn_layout.ndim(), + expected: M, + })?; + Ok((offset_range, nd_layout)) + } + + fn slice_dyn(&self, range: &[SliceItem]) -> Result<(Range, DynLayout), SliceError> { self.slice(range) } @@ -1184,14 +1180,6 @@ impl MutLayout for DynLayout { ((left_offsets, left), (right_offsets, right)) } - - fn try_slice( - &self, - range: R, - ) -> Result<(Range, DynLayout), SliceError> { - let items = range.into_slice_items(); - self.try_slice(items.as_ref()) - } } /// Trait for shapes which can be used to create a contiguous layout. @@ -1386,20 +1374,20 @@ impl_remove_dim!(5, 4); /// the number of items in `R` that are indices, as opposed to ranges. pub trait SliceWith { /// The layout produced after slicing. - type Layout: Layout; + type Layout: MutLayout; /// Slice the layout with a range. /// /// Returns a tuple of `(offset_range, sliced_layout)` where `offset_range` /// is the range of data from the original view that is used by the slice /// and `sliced_layout` is the layout of the sliced view. - fn slice_with(&self, range: R) -> (Range, Self::Layout); + fn slice_with(&self, range: R) -> Result<(Range, Self::Layout), SliceError>; } impl SliceWith for L { type Layout = DynLayout; - fn slice_with(&self, range: R) -> (Range, Self::Layout) { + fn slice_with(&self, range: R) -> Result<(Range, Self::Layout), SliceError> { self.slice_dyn(range.into_slice_items().as_ref()) } } @@ -1407,7 +1395,7 @@ impl SliceWith for L { impl SliceWith for NdLayout { type Layout = NdLayout; - fn slice_with(&self, range: R) -> (Range, Self::Layout) { + fn slice_with(&self, range: R) -> Result<(Range, Self::Layout), SliceError> { self.slice(range.into_slice_items().as_ref()) } } @@ -1417,7 +1405,7 @@ macro_rules! impl_slice_with_dynlayout { impl SliceWith for DynLayout { type Layout = DynLayout; - fn slice_with(&self, range: R) -> (Range, Self::Layout) { + fn slice_with(&self, range: R) -> Result<(Range, Self::Layout), SliceError> { self.slice_dyn(range.into_slice_items().as_ref()) } } @@ -1436,7 +1424,7 @@ macro_rules! impl_slice_with { impl SliceWith for NdLayout<$ndim> { type Layout = NdLayout<$out_ndim>; - fn slice_with(&self, range: R) -> (Range, Self::Layout) { + fn slice_with(&self, range: R) -> Result<(Range, Self::Layout), SliceError> { self.slice(range.into_slice_items().as_ref()) } } @@ -1464,7 +1452,7 @@ mod tests { use std::ops::Range; use super::OverlapPolicy; - use crate::errors::ReshapeError; + use crate::errors::{ReshapeError, SliceError}; use crate::layout::{DynLayout, Layout, MutLayout, NdLayout, ResizeLayout}; use crate::SliceItem; @@ -1736,38 +1724,66 @@ mod tests { } #[test] - #[should_panic(expected = "Slice index is invalid for tensor shape")] - fn test_slice_invalid_index() { - let layout = DynLayout::from_shape(&[3, 5]); - layout.slice(&[SliceItem::Index(4), SliceItem::Index(0)]); - } - - #[test] - #[should_panic(expected = "Slice index is invalid for tensor shape")] - fn test_slice_invalid_negative_index() { - let layout = DynLayout::from_shape(&[3, 5]); - layout.slice(&[SliceItem::Index(-4)]); - } - - #[test] - #[should_panic(expected = "Slice range is invalid for tensor shape")] - fn test_slice_invalid_range() { - let layout = DynLayout::from_shape(&[3, 5]); - layout.slice(&[SliceItem::Range((1..4).into()), SliceItem::Index(0)]); - } + fn test_slice_invalid() { + struct Case<'a> { + layout: DynLayout, + ranges: &'a [SliceItem], + expected: SliceError, + } - #[test] - #[should_panic(expected = "Slice range is invalid for tensor shape")] - fn test_slice_invalid_from_range() { - let layout = DynLayout::from_shape(&[3, 5]); - layout.slice(&[SliceItem::Range((4..).into()), SliceItem::Index(0)]); - } + let cases = [ + Case { + layout: DynLayout::from_shape(&[3, 5]), + ranges: &[SliceItem::Index(4), SliceItem::Index(0)], + expected: SliceError::InvalidIndex { + axis: 0, + index: 4, + size: 3, + }, + }, + Case { + layout: DynLayout::from_shape(&[3, 5]), + ranges: &[SliceItem::Range((1..4).into()), SliceItem::Index(0)], + expected: SliceError::InvalidRange { + axis: 0, + range: (1..4).into(), + size: 3, + }, + }, + Case { + layout: DynLayout::from_shape(&[3, 5]), + ranges: &[SliceItem::Index(-4)], + expected: SliceError::InvalidIndex { + axis: 0, + index: -4, + size: 3, + }, + }, + Case { + layout: DynLayout::from_shape(&[3, 5]), + ranges: &[SliceItem::Range((4..).into()), SliceItem::Index(0)], + expected: SliceError::InvalidRange { + axis: 0, + range: (4..).into(), + size: 3, + }, + }, + Case { + layout: DynLayout::from_shape(&[3, 5]), + ranges: &[SliceItem::full_range(), SliceItem::range(0, None, -1)], + expected: SliceError::InvalidStep { axis: 1, step: -1 }, + }, + ]; - #[test] - #[should_panic(expected = "Cannot slice with negative step")] - fn test_slice_negative_step() { - let layout = DynLayout::from_shape(&[3, 5]); - layout.slice(&[SliceItem::full_range(), SliceItem::range(0, None, -1)]); + for Case { + layout, + ranges, + expected, + } in cases + { + let result = layout.slice(ranges); + assert_eq!(result, Err(expected)); + } } #[test] diff --git a/rten-tensor/src/tensor.rs b/rten-tensor/src/tensor.rs index ba96b0e0..517a1554 100644 --- a/rten-tensor/src/tensor.rs +++ b/rten-tensor/src/tensor.rs @@ -314,11 +314,27 @@ pub trait AsView: Layout { range: R, ) -> TensorBase, >::Layout> where - Self::Layout: SliceWith, + Self::Layout: SliceWith, { self.view().slice_with(range) } + /// A variant of [`slice_with`](Self::slice_with) that returns a result + /// instead of panicking. + #[allow(clippy::type_complexity)] + fn try_slice_with( + &self, + range: R, + ) -> Result< + TensorBase, >::Layout>, + SliceError, + > + where + Self::Layout: SliceWith, + { + self.view().try_slice_with(range) + } + /// Return a slice of this tensor as an owned tensor. /// /// This is more expensive than [`slice`](AsView::slice) as it copies the @@ -781,7 +797,8 @@ impl TensorBase { range: R, ) -> NdTensorViewMut { let range = range.into_slice_items(); - let (offset_range, sliced_layout) = self.layout.slice(range.as_ref()); + let (offset_range, sliced_layout) = + self.layout.slice(range.as_ref()).expect("slice failed"); NdTensorViewMut { data: self.data.slice_mut(offset_range), layout: sliced_layout, @@ -791,7 +808,8 @@ impl TensorBase { /// Slice this tensor and return a dynamic-rank view. pub fn slice_mut_dyn(&mut self, range: R) -> TensorViewMut { let range = range.into_slice_items(); - let (offset_range, sliced_layout) = self.layout.slice_dyn(range.as_ref()); + let (offset_range, sliced_layout) = + self.layout.slice_dyn(range.as_ref()).expect("slice failed"); TensorViewMut { data: self.data.slice_mut(offset_range), layout: sliced_layout, @@ -807,13 +825,9 @@ impl TensorBase { range: R, ) -> TensorBase, >::Layout> where - L: SliceWith, + L: SliceWith, { - let (offset_range, sliced_layout) = self.layout.slice_with(range); - TensorBase { - data: self.data.slice_mut(offset_range), - layout: sliced_layout, - } + self.try_slice_with_mut(range).expect("slice failed") } /// Slice this tensor and return a dynamic-rank view. @@ -824,13 +838,30 @@ impl TensorBase { &mut self, range: R, ) -> Result, SliceError> { - let (offset_range, layout) = self.layout.try_slice(range)?; + let (offset_range, layout) = self.layout.slice_dyn(range.into_slice_items().as_ref())?; Ok(TensorBase { data: self.data.slice_mut(offset_range), layout, }) } + /// A variant of [`slice_with_mut`](Self::slice_with_mut) that returns a + /// result instead of panicking. + #[allow(clippy::type_complexity)] + pub fn try_slice_with_mut( + &mut self, + range: R, + ) -> Result, >::Layout>, SliceError> + where + L: SliceWith, + { + let (offset_range, sliced_layout) = self.layout.slice_with(range)?; + Ok(TensorBase { + data: self.data.slice_mut(offset_range), + layout: sliced_layout, + }) + } + /// Return a mutable view of this tensor. pub fn view_mut(&mut self) -> TensorBase, L> where @@ -1487,7 +1518,7 @@ impl<'a, T, L: Clone + MutLayout> TensorBase, L> { /// Slice this tensor and return a static-rank view. See [AsView::slice]. pub fn slice(&self, range: R) -> NdTensorView<'a, T, M> { let range = range.into_slice_items(); - let (offset_range, sliced_layout) = self.layout.slice(range.as_ref()); + let (offset_range, sliced_layout) = self.layout.slice(range.as_ref()).unwrap(); NdTensorView { data: self.data.slice(offset_range), layout: sliced_layout, @@ -1497,7 +1528,7 @@ impl<'a, T, L: Clone + MutLayout> TensorBase, L> { /// Slice this tensor and return a dynamic-rank view. See [AsView::slice_dyn]. pub fn slice_dyn(&self, range: R) -> TensorView<'a, T> { let range = range.into_slice_items(); - let (offset_range, sliced_layout) = self.layout.slice_dyn(range.as_ref()); + let (offset_range, sliced_layout) = self.layout.slice_dyn(range.as_ref()).unwrap(); TensorView { data: self.data.slice(offset_range), layout: sliced_layout, @@ -1510,13 +1541,26 @@ impl<'a, T, L: Clone + MutLayout> TensorBase, L> { range: R, ) -> TensorBase, >::Layout> where - L: SliceWith, + L: SliceWith, { - let (offset_range, sliced_layout) = self.layout.slice_with(range); - TensorBase { + self.try_slice_with(range).expect("slice failed") + } + + /// A variant of [`slice_with`](Self::slice_with) that returns a result + /// instead of panicking. + #[allow(clippy::type_complexity)] + pub fn try_slice_with( + &self, + range: R, + ) -> Result, >::Layout>, SliceError> + where + L: SliceWith, + { + let (offset_range, sliced_layout) = self.layout.slice_with(range)?; + Ok(TensorBase { data: self.data.slice(offset_range), layout: sliced_layout, - } + }) } /// Remove all size-one dimensions from this tensor. @@ -1620,7 +1664,7 @@ impl<'a, T, L: Clone + MutLayout> TensorBase, L> { &self, range: R, ) -> Result, SliceError> { - let (offset_range, layout) = self.layout.try_slice(range)?; + let (offset_range, layout) = self.layout.slice_dyn(range.into_slice_items().as_ref())?; Ok(TensorBase { data: self.data.slice(offset_range), layout, @@ -3806,6 +3850,22 @@ mod tests { assert!(row.is_err()); } + #[test] + fn test_try_slice_with() { + let data = vec![1., 2., 3., 4.]; + let tensor = Tensor::from_data(&[2, 2], data); + + let row = tensor.try_slice_with(0); + assert!(row.is_ok()); + assert_eq!(row.unwrap().data(), Some([1., 2.].as_slice())); + + let row = tensor.try_slice_with(1); + assert!(row.is_ok()); + + let row = tensor.try_slice_with(2); + assert!(row.is_err()); + } + #[test] fn test_try_slice_mut() { let data = vec![1., 2., 3., 4.]; @@ -3823,6 +3883,23 @@ mod tests { assert!(row.is_err()); } + #[test] + fn test_try_slice_with_mut() { + let data = vec![1., 2., 3., 4.]; + let mut tensor = Tensor::from_data(&[2, 2], data); + + let mut row = tensor.try_slice_with_mut(0).unwrap(); + row[[0]] += 1.; + row[[1]] += 1.; + assert_eq!(row.data(), Some([2., 3.].as_slice())); + + let row = tensor.try_slice_with_mut(1); + assert!(row.is_ok()); + + let row = tensor.try_slice_with(2); + assert!(row.is_err()); + } + #[test] fn test_uninit() { let mut tensor = NdTensor::uninit([2, 2]); diff --git a/src/ops/gather.rs b/src/ops/gather.rs index 86f5e953..ec96043b 100644 --- a/src/ops/gather.rs +++ b/src/ops/gather.rs @@ -40,7 +40,7 @@ pub fn gather( let mut slice_range = full_range(input.ndim()); slice_range[axis] = SliceItem::Index(*index as isize); let slice = input - .try_slice_dyn(slice_range.as_slice()) + .try_slice_with(slice_range.as_slice()) .map_err(|_| INVALID_INDEX_ERR)?; slice.to_tensor_in(pool) }; @@ -64,7 +64,7 @@ pub fn gather( out_range[axis + i] = SliceItem::Index(index_val as isize); } let in_slice = input - .try_slice_dyn(in_range.as_slice()) + .try_slice_with(in_range.as_slice()) .map_err(|_| INVALID_INDEX_ERR)?; let mut out_slice = output.slice_mut_dyn(out_range.as_slice()); out_slice.copy_from(&in_slice); @@ -304,7 +304,7 @@ pub fn gather_nd( for (out_slice, idx) in out_slices.zip(idx_slices) { let slice_items = to_slice_items(idx); let in_slice = input - .try_slice_dyn(slice_items.as_slice()) + .try_slice_with(slice_items.as_slice()) .map_err(|_| OpError::InvalidValue("Invalid index"))?; for (out, x) in out_slice.iter_mut().zip(in_slice.iter()) {