Skip to content

Commit 8b862be

Browse files
committed
Add multislice! macro
1 parent 34d738a commit 8b862be

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

0 commit comments

Comments
 (0)