Skip to content

Commit 4456819

Browse files
committed
Add multislice! macro
1 parent 58225b9 commit 4456819

File tree

3 files changed

+387
-2
lines changed

3 files changed

+387
-2
lines changed

src/lib.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,9 +447,12 @@ pub type Ixs = isize;
447447
/// [`.slice_move()`]: #method.slice_move
448448
/// [`.slice_inplace()`]: #method.slice_inplace
449449
///
450+
/// It's possible to take multiple simultaneous *mutable* slices with the
451+
/// [`multislice!()`](macro.multislice!.html) macro.
452+
///
450453
/// ```
451-
/// // import the s![] macro
452-
/// #[macro_use(s)]
454+
/// // import the multislice!() and s![] macros
455+
/// #[macro_use(multislice, s)]
453456
/// extern crate ndarray;
454457
///
455458
/// use ndarray::{arr2, arr3};
@@ -499,6 +502,20 @@ pub type Ixs = isize;
499502
/// [12, 11, 10]]);
500503
/// assert_eq!(f, g);
501504
/// assert_eq!(f.shape(), &[2, 3]);
505+
///
506+
/// // Let's take two disjoint, mutable slices of a matrix with
507+
/// //
508+
/// // - One containing all the even-index columns in the matrix
509+
/// // - One containing all the odd-index columns in the matrix
510+
/// let mut h = arr2(&[[0, 1, 2, 3],
511+
/// [4, 5, 6, 7]]);
512+
/// let (s0, s1) = multislice!(h, (mut s![.., ..;2], mut s![.., 1..;2]));
513+
/// let i = arr2(&[[0, 2],
514+
/// [4, 6]]);
515+
/// let j = arr2(&[[1, 3],
516+
/// [5, 7]]);
517+
/// assert_eq!(s0, i);
518+
/// assert_eq!(s1, j);
502519
/// }
503520
/// ```
504521
///

src/slice.rs

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,3 +627,226 @@ macro_rules! s(
627627
&*&s![@parse ::std::marker::PhantomData::<$crate::Ix0>, [] $($t)*]
628628
};
629629
);
630+
631+
/// Take multiple slices simultaneously.
632+
///
633+
/// This macro makes it possible to take multiple slices of the same array, as
634+
/// long as Rust's aliasing rules are followed for *elements* in the slices.
635+
/// For example, it's possible to take two disjoint, mutable slices of an
636+
/// array, with one referencing the even-index elements and the other
637+
/// referencing the odd-index elements. If you tried to achieve this by calling
638+
/// `.slice_mut()` twice, the borrow checker would complain about mutably
639+
/// borrowing the array twice (even though it's safe as long as the slices are
640+
/// disjoint).
641+
///
642+
/// The syntax is `multislice!(` *expression, (pattern [, pattern [, …]])* `)`,
643+
/// where *expression* evaluates to an `ArrayBase<S, D>` where `S: DataMut`,
644+
/// and `pattern` is one of the following:
645+
///
646+
/// * `mut expr`: creates an `ArrayViewMut`, where `expr` evaluates to a
647+
/// `&SliceInfo` instance used to slice the array.
648+
/// * `expr`: creates an `ArrayView`, where `expr` evaluates to a `&SliceInfo`
649+
/// instance used to slice the array.
650+
///
651+
/// **Note** that this macro always mutably borrows the array even if there are
652+
/// no `mut` patterns. If all you want to do is take read-only slices, you
653+
/// don't need `multislice!()`; just call
654+
/// [`.slice()`](struct.ArrayBase.html#method.slice) multiple times instead.
655+
///
656+
/// `multislice!()` follows Rust's aliasing rules:
657+
///
658+
/// * An `ArrayViewMut` and `ArrayView` cannot reference the same element.
659+
/// * Two `ArrayViewMut` cannot reference the same element.
660+
/// * Two `ArrayView` can reference the same element.
661+
///
662+
/// **Panics** at runtime if any of the aliasing rules is violated.
663+
///
664+
/// See also [*Slicing*](struct.ArrayBase.html#slicing).
665+
///
666+
/// # Examples
667+
///
668+
/// In this example, there are two overlapping read-only slices, and two
669+
/// disjoint mutable slices. Neither of the mutable slices intersects any of
670+
/// the other slices.
671+
///
672+
/// ```
673+
/// #[macro_use]
674+
/// extern crate ndarray;
675+
///
676+
/// use ndarray::prelude::*;
677+
///
678+
/// # fn main() {
679+
/// let mut arr = Array1::from_iter(0..12);
680+
/// let (a, b, c, d) = multislice!(arr, (s![0..5], mut s![6..;2], s![1..6], mut s![7..;2]));
681+
/// assert_eq!(a, array![0, 1, 2, 3, 4]);
682+
/// assert_eq!(b, array![6, 8, 10]);
683+
/// assert_eq!(c, array![1, 2, 3, 4, 5]);
684+
/// assert_eq!(d, array![7, 9, 11]);
685+
/// # }
686+
/// ```
687+
///
688+
/// These examples panic because they don't follow the aliasing rules:
689+
///
690+
/// * `ArrayViewMut` and `ArrayView` cannot reference the same element.
691+
///
692+
/// ```should_panic
693+
/// # #[macro_use] extern crate ndarray;
694+
/// # use ndarray::prelude::*;
695+
/// # fn main() {
696+
/// let mut arr = Array1::from_iter(0..12);
697+
/// multislice!(arr, (s![0..5], mut s![1..;2])); // panic!
698+
/// # }
699+
/// ```
700+
///
701+
/// * Two `ArrayViewMut` cannot reference the same element.
702+
///
703+
/// ```should_panic
704+
/// # #[macro_use] extern crate ndarray;
705+
/// # use ndarray::prelude::*;
706+
/// # fn main() {
707+
/// let mut arr = Array1::from_iter(0..12);
708+
/// multislice!(arr, (mut s![0..5], mut s![1..;2])); // panic!
709+
/// # }
710+
/// ```
711+
#[macro_export]
712+
macro_rules! multislice(
713+
(
714+
@check $view:expr,
715+
$info:expr,
716+
()
717+
) => {};
718+
// Check that $info doesn't intersect $other.
719+
(
720+
@check $view:expr,
721+
$info:expr,
722+
($other:expr,)
723+
) => {
724+
assert!(
725+
!$crate::slices_intersect(&$view.raw_dim(), $info, $other),
726+
"Slice {:?} must not intersect slice {:?}", $info, $other
727+
)
728+
};
729+
// Check that $info doesn't intersect any of the other info in the tuple.
730+
(
731+
@check $view:expr,
732+
$info:expr,
733+
($other:expr, $($more:tt)*)
734+
) => {
735+
{
736+
multislice!(@check $view, $info, ($other,));
737+
multislice!(@check $view, $info, ($($more)*));
738+
}
739+
};
740+
// Parse last slice (mutable), no trailing comma.
741+
(
742+
@parse $view:expr,
743+
($($sliced:tt)*),
744+
($($mut_info:tt)*),
745+
($($immut_info:tt)*),
746+
(mut $info:expr)
747+
) => {
748+
match $info {
749+
info => {
750+
multislice!(@check $view, info, ($($mut_info)*));
751+
multislice!(@check $view, info, ($($immut_info)*));
752+
($($sliced)* unsafe { $view.aliasing_view_mut() }.slice_move(info))
753+
}
754+
}
755+
};
756+
// Parse last slice (read-only), no trailing comma.
757+
(
758+
@parse $view:expr,
759+
($($sliced:tt)*),
760+
($($mut_info:tt)*),
761+
($($immut_info:tt)*),
762+
($info:expr)
763+
) => {
764+
match $info {
765+
info => {
766+
multislice!(@check $view, info, ($($mut_info)*));
767+
($($sliced)* unsafe { $view.aliasing_view() }.slice_move(info))
768+
}
769+
}
770+
};
771+
// Parse last slice (mutable), with trailing comma.
772+
(
773+
@parse $view:expr,
774+
($($sliced:tt)*),
775+
($($mut_info:tt)*),
776+
($($immut_info:tt)*),
777+
(mut $info:expr,)
778+
) => {
779+
match $info {
780+
info => {
781+
multislice!(@check $view, info, ($($mut_info)*));
782+
multislice!(@check $view, info, ($($immut_info)*));
783+
($($sliced)* unsafe { $view.aliasing_view_mut() }.slice_move(info))
784+
}
785+
}
786+
};
787+
// Parse last slice (read-only), with trailing comma.
788+
(
789+
@parse $view:expr,
790+
($($sliced:tt)*),
791+
($($mut_info:tt)*),
792+
($($immut_info:tt)*),
793+
($info:expr,)
794+
) => {
795+
match $info {
796+
info => {
797+
multislice!(@check $view, info, ($($mut_info)*));
798+
($($sliced)* unsafe { $view.aliasing_view() }.slice_move(info))
799+
}
800+
}
801+
};
802+
// Parse a mutable slice.
803+
(
804+
@parse $view:expr,
805+
($($sliced:tt)*),
806+
($($mut_info:tt)*),
807+
($($immut_info:tt)*),
808+
(mut $info:expr, $($t:tt)*)
809+
) => {
810+
match $info {
811+
info => {
812+
multislice!(@check $view, info, ($($mut_info)*));
813+
multislice!(@check $view, info, ($($immut_info)*));
814+
multislice!(
815+
@parse $view,
816+
($($sliced)* unsafe { $view.aliasing_view_mut() }.slice_move(info),),
817+
($($mut_info)* info,),
818+
($($immut_info)*),
819+
($($t)*)
820+
)
821+
}
822+
}
823+
};
824+
// Parse a read-only slice.
825+
(
826+
@parse $view:expr,
827+
($($sliced:tt)*),
828+
($($mut_info:tt)*),
829+
($($immut_info:tt)*),
830+
($info:expr, $($t:tt)*)
831+
) => {
832+
match $info {
833+
info => {
834+
multislice!(@check $view, info, ($($mut_info)*));
835+
multislice!(
836+
@parse $view,
837+
($($sliced)* unsafe { $view.aliasing_view() }.slice_move(info),),
838+
($($mut_info)*),
839+
($($immut_info)* info,),
840+
($($t)*)
841+
)
842+
}
843+
}
844+
};
845+
// Entry point.
846+
($arr:expr, ($($t:tt)*)) => {
847+
{
848+
let view = $crate::ArrayBase::view_mut(&mut $arr);
849+
multislice!(@parse view, (), (), (), ($($t)*))
850+
}
851+
};
852+
);

0 commit comments

Comments
 (0)