xuko-core 0.1.0

Core routines, and data structures for xuko
Documentation
#![expect(
    unsafe_op_in_unsafe_fn,
    reason = "Array requires unsafe code in some places"
)]

//! Variable length array

use std::alloc;
use std::ops::{Deref, DerefMut, Index, IndexMut};

/// An error that can occur when creating an [`Array`]
#[derive(Debug, thiserror::Error)]
pub enum ArrayCreationError {
    #[error("allocation error: {0}")]
    AllocationError(String),

    #[error("layout error: {0}")]
    LayoutError(#[from] alloc::LayoutError),
}

/// Variable length array.
///
/// The purpose of this data structure is to provide an alternative to [`Vec`] with a static length.
/// Use a normal array if you know the length at compile time.
///
/// A use for [`Array`] is where you need to read from a [`std::io::Read`]er to a [`Vec`] using
/// the [`std::io::Read::read`] function, this wouldn't work because some elements of a [`Vec`] may be
/// uninitialized whereas [`Array`] functions just like a static compile-time known array.
///
/// # Examples
///
/// ```
/// use xuko_core::array::Array;
///
/// let mut array = Array::new(5).expect("Failed to create array");
///
/// array[0] = 1;
/// array[1] = 2;
/// array[2] = 3;
/// array[3] = 4;
/// array[4] = 5;
/// ```
pub struct Array<T> {
    ptr: *mut T,
    size: usize,
}

impl<T> Array<T> {
    /// Allocate an [`Array`]
    ///
    /// Returns an [`ArrayCreationError`] when the allocation fails.
    pub fn new(size: usize) -> Result<Self, ArrayCreationError> {
        unsafe {
            // TODO: Should this just panic on failed memory allocation?
            // The chances of that happening are low anyways.
            // Well if you want robust code it'd be annoying if this has the possibility of panicing.
            let layout = alloc::Layout::array::<T>(size)?;
            let ptr = alloc::alloc(layout) as *mut T;

            if ptr.is_null() {
                return Err(ArrayCreationError::AllocationError(
                    "null pointer".to_owned(),
                ));
            }

            Ok(Self { ptr, size })
        }
    }

    /// Get the size of the array
    #[must_use]
    pub const fn size(&self) -> usize {
        self.size
    }

    /// Return the array as an immutable pointer
    #[must_use]
    pub const fn as_ptr(&self) -> *const T {
        self.ptr
    }

    /// Return the array as a mutable pointer
    #[must_use]
    pub const fn as_mut_ptr(&self) -> *mut T {
        self.ptr
    }

    /// Set value at `index` to `value`
    ///
    /// # Panics
    ///
    /// Panics when `index` is out of bounds
    pub fn set(&mut self, index: usize, value: T) {
        if index >= self.size {
            panic!("index out of bounds");
        }

        // SAFETY: it is ensured that whatever this pointer points to is inside of the `Array` by
        // the previous statement
        unsafe { *(self.ptr.add(index)) = value }
    }

    /// Get value at index `index`
    #[must_use]
    pub const fn get(&self, index: usize) -> Option<&T> {
        if index >= self.size {
            return None;
        }

        // SAFETY: it is ensured that whatever this pointer points to is inside of the `Array` by
        // the previous statement
        unsafe { Some(&(*(self.ptr.add(index)))) }
    }

    /// Get value at index `index`
    #[must_use]
    pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
        if index >= self.size {
            return None;
        }

        // SAFETY: it is ensured that whatever this pointer points to is inside of the `Array` by
        // the previous statement
        unsafe { Some(&mut (*(self.ptr.add(index)))) }
    }

    /// Returns an immutable pointer to value at `index`.
    ///
    /// # Safety
    ///
    /// The pointer is obtained using unchecked pointer arithmetic.
    #[must_use]
    pub const unsafe fn get_ptr(&self, index: usize) -> *const T {
        self.ptr.add(index) as *const T
    }

    /// Returns a mutable pointer to value at `index`.
    ///
    /// # Safety
    ///
    /// The pointer is obtained using unchecked pointer arithmetic.
    #[must_use]
    pub unsafe fn get_ptr_mut(&mut self, index: usize) -> *mut T {
        self.ptr.add(index)
    }

    /// Return an iterator over the array
    #[must_use]
    pub fn iter(&self) -> iter::Iter<'_, T> {
        iter::Iter::new(self)
    }

    /// Return a mutable iterator over the array
    #[must_use]
    pub fn iter_mut(&mut self) -> iter::IterMut<'_, T> {
        iter::IterMut::new(self)
    }
}

impl<T> Drop for Array<T> {
    fn drop(&mut self) {
        unsafe {
            self.ptr.drop_in_place();
        }
    }
}

impl<T> Deref for Array<T> {
    type Target = [T];

    fn deref(&self) -> &Self::Target {
        // SAFETY: the length is known in the struct so this wont result in UB
        unsafe { std::slice::from_raw_parts(self.ptr, self.size) }
    }
}

impl<T> DerefMut for Array<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        // SAFETY: the length is known in the struct so this wont result in UB
        unsafe { std::slice::from_raw_parts_mut(self.ptr, self.size) }
    }
}

impl<T> AsRef<[T]> for Array<T> {
    fn as_ref(&self) -> &[T] {
        self
    }
}

impl<T> AsMut<[T]> for Array<T> {
    fn as_mut(&mut self) -> &mut [T] {
        &mut *self
    }
}

/// [`std::io::Write`] is implemented for any [`Array<u8>`]
impl std::io::Write for Array<u8> {
    // TODO: is this implementation even good?
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        let mut index = 0;
        for b in buf {
            self[index] = *b;
            index += 1;
        }
        Ok(index + 1)
    }

    fn flush(&mut self) -> std::io::Result<()> {
        Ok(())
    }
}

impl<T> Index<usize> for Array<T> {
    type Output = T;

    fn index(&self, index: usize) -> &Self::Output {
        self.get(index).expect("index out of bounds")
    }
}

impl<T> IndexMut<usize> for Array<T> {
    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
        self.get_mut(index).expect("index out of bounds")
    }
}

impl<T: Clone> Clone for Array<T> {
    fn clone(&self) -> Self {
        let mut array = Array::new(self.size).expect("allocation failed");

        for (i, v) in self.iter().enumerate() {
            array[i] = v.clone();
        }

        array
    }
}

impl<T> IntoIterator for Array<T> {
    type Item = T;
    type IntoIter = iter::IntoIter<T>;

    fn into_iter(self) -> Self::IntoIter {
        iter::IntoIter::new(self)
    }
}

impl<'a, T> IntoIterator for &'a Array<T> {
    type Item = &'a T;
    type IntoIter = iter::Iter<'a, T>;

    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}

impl<'a, T> IntoIterator for &'a mut Array<T> {
    type Item = &'a mut T;
    type IntoIter = iter::IterMut<'a, T>;

    fn into_iter(self) -> Self::IntoIter {
        self.iter_mut()
    }
}

pub mod iter {
    use super::Array;
    use std::marker::PhantomData;

    /// Immutable [`Array`] iterator.
    pub struct Iter<'a, T> {
        _marker: PhantomData<&'a T>,
        ptr: *const T,
        end: *const T,
    }

    impl<'a, T> Iter<'a, T> {
        pub(crate) fn new(array: &'a Array<T>) -> Self {
            let ptr = array.ptr;
            Self {
                _marker: PhantomData,
                ptr,
                end: unsafe { ptr.add(array.size) },
            }
        }
    }

    impl<'a, T> Iterator for Iter<'a, T> {
        type Item = &'a T;

        fn next(&mut self) -> Option<Self::Item> {
            if self.ptr == self.end {
                None
            } else {
                unsafe {
                    let ptr = self.ptr;
                    self.ptr = self.ptr.add(1);
                    Some(&*ptr)
                }
            }
        }
    }

    /// Mutable [`Array`] iterator
    pub struct IterMut<'a, T> {
        _marker: PhantomData<&'a T>,
        ptr: *mut T,
        end: *mut T,
    }

    impl<'a, T> IterMut<'a, T> {
        pub(crate) fn new(array: &'a Array<T>) -> Self {
            let ptr = array.ptr;
            Self {
                _marker: PhantomData,
                ptr,
                end: unsafe { ptr.add(array.size) },
            }
        }
    }

    impl<'a, T> Iterator for IterMut<'a, T> {
        type Item = &'a mut T;

        fn next(&mut self) -> Option<Self::Item> {
            if self.ptr == self.end {
                None
            } else {
                unsafe {
                    let ptr = self.ptr;
                    self.ptr = self.ptr.add(1);
                    Some(&mut *ptr)
                }
            }
        }
    }

    /// Owned [`Array`] Iterator
    pub struct IntoIter<T> {
        _array: Array<T>,
        ptr: *const T,
        end: *const T,
    }

    impl<T> IntoIter<T> {
        pub(crate) fn new(array: Array<T>) -> Self {
            unsafe {
                let ptr = array.ptr.cast_const();
                let end = ptr.add(array.size);
                Self {
                    _array: array,
                    ptr,
                    end,
                }
            }
        }
    }

    impl<T> Iterator for IntoIter<T> {
        type Item = T;

        fn next(&mut self) -> Option<Self::Item> {
            if self.ptr == self.end {
                None
            } else {
                unsafe {
                    let ptr = self.ptr;
                    self.ptr = self.ptr.add(1);
                    Some(ptr.read())
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn array_basics() {
        let mut array = Array::new(5).expect("failed to allocate");
        array[0] = 1;
        array[1] = 2;
        array[2] = 3;
        array[3] = 4;
        array[4] = 5;

        for (i, v) in array.iter().enumerate() {
            match i {
                0 => assert_eq!(*v, 1),
                1 => assert_eq!(*v, 2),
                2 => assert_eq!(*v, 3),
                3 => assert_eq!(*v, 4),
                4 => assert_eq!(*v, 5),
                _ => unreachable!(),
            }
        }
    }
}