1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#![no_std]
//! This crate contains functionality to move a slice within an array around.
//! It only uses safe functions, and acts efficiently by using the
//! [`split_at_mut`][split-at-mut] and
//! [`rotate_left`][rotate-left]/[`rotate_right`][rotate-right] functions.
//! 
//! This crate also has a focus on being `no_std`, to allow this functionality
//! in all cases where it is required.
//! 
//! The main feature this crate provides is implementing `moveslice` functions
//! for any and all slices/arrays. In effect, it implements it on any type that 
//! also implements the AsMut<[T]> trait. This includes slices and vectors.
//! 
//! # Examples:
//! 
//! ```
//! use moveslice::{Moveslice, Error};
//! 
//! let mut arr = [1,2,3,4,5,6,7,8,9];
//! 
//! // The following moves the slice 3..6 to index 1.
//! // In effect, it moves [4,5,6] over to where [2] is.
//! arr.moveslice(3..6, 1);
//! assert_eq!(arr, [1,4,5,6,2,3,7,8,9]);
//! 
//! // The following moves the slice 3..6 to index 6.
//! // In effect, it moves [6,2,3] over to where [7] is.
//! arr.moveslice(3..6, 6);
//! assert_eq!(arr, [1,4,5,7,8,9,6,2,3]);
//! 
//! // The following attempts to move the slice beyond boundaries.
//! // The index given is 7, which exists in the array, but the 
//! // last element of the chunk will not fit (7 + 3 = 10 > 9).
//! // Therefore, the following should fail.
//! # #[should_panic]
//! # fn main() {
//! # let mut arr = [1,2,3,4,5,6,7,8,9];
//! arr.moveslice(3..6, 7); // will panic
//! # }
//! 
//! // Panicking on failure however can prove to be not ideal.
//! // If instead of panicking, you prefer a `Result`, use 
//! // `try_moveslice`.
//! let res = arr.try_moveslice(3..6, 7);
//! assert!(res.is_err());
//! 
//! // Moveslice also comes with its own `Error` enum, with diagnostic
//! // information to help debugging. The line before would have triggered
//! // an OutOfBoundsMove error. The following line would trigger the 
//! // InvalidBounds error.
//! let res = arr.try_moveslice(9..10, 7);
//! assert!(if let Err(Error::InvalidBounds{..}) = res {true} else {false});
//! 
//! // You could pass the destination as the same value as chunk.0.
//! // However this would mean nothing is moved.
//! // This doesn't panic, but it's a no-op.
//! arr.moveslice(0..3, 0);
//! ```
//! 
//! [split-at-mut]: https://doc.rust-lang.org/std/primitive.slice.html#method.split_at_mut
//! [rotate-left]: https://doc.rust-lang.org/std/primitive.slice.html#method.rotate_left
//! [rotate-right]: https://doc.rust-lang.org/std/primitive.slice.html#method.rotate_right

use core::ops::Bound::*;
use core::ops::RangeBounds;

/// This Error enum has a single variant, which is used to return additional information about
/// the out of bounds error, to help diagnostics.
/// 
/// Is used/returned by `try_moveslice`.
#[derive(Debug)]
pub enum Error {
    /// This error signifies an out of bounds error.
    /// It also contains the length of the slice, and 
    /// the supposed location of where the chunk would have been.
    /// 
    /// For example:
    /// `OutOfBoundsMove {len: 10, dest: (8,11)}`
    OutOfBoundsMove {
        /// The length of the array/slice being modified.
        len: usize,
        /// The location of where the chunk would have ended up.
        dest: (usize, usize)
    },

    /// This error signifies an invalid bounds error.
    /// If the bounds passed are already out of bounds, this 
    /// error is returned instead. This is to differentiate
    /// between the two out-of-bounds cases.
    InvalidBounds {
        // The length of the array/slice being modified.
        len: usize,
        // The effective bounds passed in.
        bounds: (usize, usize)
    }
}

/// A trait declaring the `moveslice` and `try_moveslice` functions.
/// Used to implement the functions on all slices. 
pub trait Moveslice<T, R> {
    /// Specifies the type of the destination index.
    type Target; 

    /// Specifies the errors being returned.
    type Err;

    /// Moves a slice within an array/slice around.
    /// 
    /// - `bounds` - specifies the range of where the subslice is. Examples: 3..5, 5..=8
    /// - `destination` - specifies where the subslice should be moved to.
    fn moveslice(&mut self, bounds: R, destination: Self::Target)
        where R: RangeBounds<usize>;

    /// Similar to `moveslice`, except it does not panic, returning a `Result` instead.
    fn try_moveslice(&mut self, bounds: R, destination: Self::Target) -> Result<(), Self::Err>
        where R: RangeBounds<usize>;
}

/// Implements the moveslice functions on all slices.
impl<T: 'static,R,A> Moveslice<T,R> for A where A: AsMut<[T]> {
    type Target = usize;
    type Err = Error;

    fn moveslice(&mut self, bounds: R, destination: Self::Target)
    where R: RangeBounds<usize> 
    {
        let res = self.try_moveslice(bounds, destination);
        if let Err(Error::OutOfBoundsMove{len, dest: (x,y)}) = res {
            panic!("Movement goes beyond bounds. [len = {}, destination = {}..{}]", len, x, y);
        }
        else if let Err(Error::InvalidBounds{len, bounds: (x,y)}) = res {
            panic!("Bounds passed go beyond slice length. [len = {}, bounds = {}..{}]", len, x, y);
        }
    }

    fn try_moveslice(&mut self, bounds: R, destination: Self::Target) -> Result<(), Self::Err>
    where R: RangeBounds<usize> 
    {
        let slice = self.as_mut();
        let startbound = bounds.start_bound();
        let endbound = bounds.end_bound();
        let x = if let Included(x) = startbound {*x} else {0};
        let y = if let Excluded(x) = endbound {*x}
                else if let Included(x) = endbound {x+1} 
                else {slice.len()};
        let chunk = (x,y);

        if chunk.0 > slice.len() || chunk.1 > slice.len() {
            return Err(Error::InvalidBounds {
                len: slice.len(),
                bounds: chunk
            });
        }

        if destination > chunk.0 {
            let chunksize = chunk.1 - chunk.0;
            let index1 = chunk.0;
            let index2 = destination + chunksize - index1;

            let (_, mid) = slice.split_at_mut(index1);

            let mid = if index2 <= mid.len() {
                mid.split_at_mut(index2).0
            } else {
                return Err(Error::OutOfBoundsMove {
                    len: slice.len(),
                    dest: (destination, destination + chunksize),
                });
            };

            mid.rotate_left(chunk.1-chunk.0);
        } else if destination < chunk.0 {
            let index1 = destination;
            let index2 = chunk.1 - destination;

            let (_, mid) = slice.split_at_mut(index1);

            let mid = mid.split_at_mut(index2).0;

            mid.rotate_right(chunk.1-chunk.0);
        }

        Ok(())
    }
}