Skip to main content

mlx_rs/array/
mod.rs

1use crate::{
2    dtype::Dtype,
3    error::{AsSliceError, ConversionError},
4    sealed::Sealed,
5    utils::{guard::Guarded, SUCCESS},
6    Stream,
7};
8use element::FromSliceElement;
9use mlx_internal_macros::default_device;
10use mlx_sys::mlx_array;
11use num_complex::Complex;
12use std::{
13    ffi::{c_void, CStr},
14    iter::Sum,
15};
16
17mod element;
18mod operators;
19
20cfg_safetensors! {
21    mod safetensors;
22}
23
24pub use element::ArrayElement;
25
26// Not using Complex64 because `num_complex::Complex64` is actually Complex<f64>
27
28/// Type alias for `num_complex::Complex<f32>`.
29#[allow(non_camel_case_types)]
30pub type complex64 = Complex<f32>;
31
32/// An n-dimensional array.
33#[repr(transparent)]
34pub struct Array {
35    c_array: mlx_array,
36}
37
38impl Sealed for Array {}
39
40impl Sealed for &Array {}
41
42impl std::fmt::Debug for Array {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        write!(f, "{self}")
45    }
46}
47
48impl std::fmt::Display for Array {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        unsafe {
51            let mut mlx_str = mlx_sys::mlx_string_new();
52            let status = mlx_sys::mlx_array_tostring(&mut mlx_str as *mut _, self.as_ptr());
53            if status != SUCCESS {
54                return Err(std::fmt::Error);
55            }
56            let ptr = mlx_sys::mlx_string_data(mlx_str);
57            let c_str = CStr::from_ptr(ptr);
58            write!(f, "{}", c_str.to_str().map_err(|_| std::fmt::Error)?)?;
59            mlx_sys::mlx_string_free(mlx_str);
60            Ok(())
61        }
62    }
63}
64
65impl Drop for Array {
66    fn drop(&mut self) {
67        // TODO: check memory leak with some tool?
68
69        // Decrease the reference count
70        unsafe { mlx_sys::mlx_array_free(self.as_ptr()) };
71    }
72}
73
74// SAFETY: MLX 0.32.2 supports moving array handles while operations use per-thread default streams.
75unsafe impl Send for Array {}
76
77impl Array {
78    /// Create a new array from an existing mlx_array pointer.
79    ///
80    /// # Safety
81    ///
82    /// `c_array` must be a valid, independently owned MLX array handle. [`Array`] takes ownership
83    /// of that handle and frees it on drop. A borrowed handle, such as one returned by
84    /// [`Array::as_ptr`], must first be duplicated into a fresh handle with
85    /// [`mlx_sys::mlx_array_set`].
86    ///
87    /// ```no_run
88    /// use mlx_rs::Array;
89    ///
90    /// let source = Array::from_int(1);
91    /// let duplicate = unsafe {
92    ///     let mut handle = mlx_sys::mlx_array_new();
93    ///     assert_eq!(mlx_sys::mlx_array_set(&mut handle, source.as_ptr()), 0);
94    ///     Array::from_ptr(handle)
95    /// };
96    /// assert_eq!(duplicate.item_exact::<i32>(), 1);
97    /// ```
98    pub unsafe fn from_ptr(c_array: mlx_array) -> Array {
99        Self { c_array }
100    }
101
102    /// Get the underlying mlx_array pointer.
103    pub fn as_ptr(&self) -> mlx_array {
104        self.c_array
105    }
106
107    /// New array from a bool scalar.
108    pub fn from_bool(val: bool) -> Array {
109        let c_array = unsafe { mlx_sys::mlx_array_new_bool(val) };
110        Array { c_array }
111    }
112
113    /// New array from an int scalar.
114    pub fn from_int(val: i32) -> Array {
115        let c_array = unsafe { mlx_sys::mlx_array_new_int(val) };
116        Array { c_array }
117    }
118
119    /// New array from a f32 scalar.
120    pub fn from_f32(val: f32) -> Array {
121        let c_array = unsafe { mlx_sys::mlx_array_new_float32(val) };
122        Array { c_array }
123    }
124
125    /// New array from a f64 scalar.
126    pub fn from_f64(val: f64) -> Array {
127        let c_array = unsafe { mlx_sys::mlx_array_new_float64(val) };
128        Array { c_array }
129    }
130
131    /// New array from a complex scalar.
132    pub fn from_complex(val: complex64) -> Array {
133        let c_array = unsafe { mlx_sys::mlx_array_new_complex(val.re, val.im) };
134        Array { c_array }
135    }
136
137    /// New array from existing buffer.
138    ///
139    /// Please note that floating point literals are treated as f32 instead of
140    /// f64. Use [`Array::from_slice_f64`] for f64.
141    ///
142    /// # Parameters
143    ///
144    /// - `data`: A buffer which will be copied.
145    /// - `shape`: Shape of the array.
146    ///
147    /// # Panic
148    ///
149    /// - Panics if the product of the shape is not equal to the length of the
150    ///   data.
151    /// - Panics if the shape is too large.
152    pub fn from_slice<T: FromSliceElement>(data: &[T], shape: &[i32]) -> Self {
153        // Validate data size and shape
154        assert_eq!(data.len(), shape.iter().product::<i32>() as usize);
155
156        unsafe { Self::from_raw_data(data.as_ptr() as *const c_void, shape, T::DTYPE) }
157    }
158
159    /// New array from a slice of f64.
160    ///
161    /// A separate method is provided for f64 because f64 is not supported on GPU
162    /// and rust defaults to f64 for floating point literals
163    pub fn from_slice_f64(data: &[f64], shape: &[i32]) -> Self {
164        // Validate data size and shape
165        assert_eq!(data.len(), shape.iter().product::<i32>() as usize);
166
167        unsafe { Self::from_raw_data(data.as_ptr() as *const c_void, shape, Dtype::Float64) }
168    }
169
170    /// Create a new array from raw data buffer.
171    ///
172    /// This is a convenience wrapper around [`mlx_sy::mlx_array_new_data`].
173    ///
174    /// # Safety
175    ///
176    /// This is unsafe because the caller must ensure that the data buffer is valid and that the
177    /// shape is correct.
178    #[inline]
179    pub unsafe fn from_raw_data(data: *const c_void, shape: &[i32], dtype: Dtype) -> Self {
180        let dim = if shape.len() > i32::MAX as usize {
181            panic!("Shape is too large")
182        } else {
183            shape.len() as i32
184        };
185
186        let c_array = mlx_sys::mlx_array_new_data(data, shape.as_ptr(), dim, dtype.into());
187        Array { c_array }
188    }
189
190    /// New array from an iterator.
191    ///
192    /// Please note that floating point literals are treated as f32 instead of
193    /// f64. Use [`Array::from_iter_f64`] for f64.
194    ///
195    /// This is a convenience method that is equivalent to
196    ///
197    /// ```text
198    /// let data: Vec<T> = iter.collect();
199    /// Array::from_slice(&data, shape)
200    /// ```
201    ///
202    /// # Example
203    ///
204    /// ```rust
205    /// use mlx_rs::Array;
206    ///
207    /// let data = vec![1i32, 2, 3, 4, 5];
208    /// let mut array = Array::from_iter(data.clone(), &[5]);
209    /// assert_eq!(array.as_slice::<i32>(), &data[..]);
210    /// ```
211    pub fn from_iter<I: IntoIterator<Item = T>, T: FromSliceElement>(
212        iter: I,
213        shape: &[i32],
214    ) -> Self {
215        let data: Vec<T> = iter.into_iter().collect();
216        Self::from_slice(&data, shape)
217    }
218
219    /// New array from an iterator of f64.
220    ///
221    /// A separate method is provided for f64 because f64 is not supported on GPU
222    /// and rust defaults to f64 for floating point literals
223    pub fn from_iter_f64<I: IntoIterator<Item = f64>>(iter: I, shape: &[i32]) -> Self {
224        let data: Vec<f64> = iter.into_iter().collect();
225        Self::from_slice_f64(&data, shape)
226    }
227
228    /// The size of the array’s datatype in bytes.
229    pub fn item_size(&self) -> usize {
230        unsafe { mlx_sys::mlx_array_itemsize(self.as_ptr()) }
231    }
232
233    /// Number of elements in the array.
234    pub fn size(&self) -> usize {
235        unsafe { mlx_sys::mlx_array_size(self.as_ptr()) }
236    }
237
238    /// The strides of the array.
239    pub fn strides(&self) -> &[usize] {
240        let ndim = self.ndim();
241        if ndim == 0 {
242            // The data pointer may be null which would panic even if len is 0
243            return &[];
244        }
245
246        unsafe {
247            let data = mlx_sys::mlx_array_strides(self.as_ptr());
248            std::slice::from_raw_parts(data, ndim)
249        }
250    }
251
252    /// The number of bytes in the array.
253    pub fn nbytes(&self) -> usize {
254        unsafe { mlx_sys::mlx_array_nbytes(self.as_ptr()) }
255    }
256
257    /// The array’s dimension.
258    pub fn ndim(&self) -> usize {
259        unsafe { mlx_sys::mlx_array_ndim(self.as_ptr()) }
260    }
261
262    /// The shape of the array.
263    ///
264    /// Returns: a pointer to the sizes of each dimension.
265    pub fn shape(&self) -> &[i32] {
266        let ndim = self.ndim();
267        if ndim == 0 {
268            // The data pointer may be null which would panic even if len is 0
269            return &[];
270        }
271
272        unsafe {
273            let data = mlx_sys::mlx_array_shape(self.as_ptr());
274            std::slice::from_raw_parts(data, ndim)
275        }
276    }
277
278    /// The shape of the array in a particular dimension.
279    ///
280    /// # Panic
281    ///
282    /// - Panics if the array is scalar.
283    /// - Panics if `dim` is negative and `dim + ndim` overflows
284    /// - Panics if the dimension is out of bounds.
285    pub fn dim(&self, dim: i32) -> i32 {
286        let dim = if dim.is_negative() {
287            (self.ndim() as i32).checked_add(dim).unwrap()
288        } else {
289            dim
290        };
291
292        // This will panic on a scalar array
293        unsafe { mlx_sys::mlx_array_dim(self.as_ptr(), dim) }
294    }
295
296    /// The array element type.
297    pub fn dtype(&self) -> Dtype {
298        let dtype = unsafe { mlx_sys::mlx_array_dtype(self.as_ptr()) };
299        Dtype::try_from(dtype).unwrap()
300    }
301
302    /// Evaluate the array.
303    pub fn eval(&self) -> crate::error::Result<()> {
304        <() as Guarded>::try_from_op(|_| unsafe { mlx_sys::mlx_array_eval(self.as_ptr()) })
305    }
306
307    /// Access the value of a scalar array without dtype conversion.
308    ///
309    /// This evaluates the array and panics if evaluation fails, the array is not scalar, or `T`
310    /// does not exactly match the array dtype.
311    pub fn item_exact<T: ArrayElement>(&self) -> T {
312        self.try_item_exact().unwrap()
313    }
314
315    /// Access the value of a scalar array without dtype conversion.
316    ///
317    /// This evaluates the array and returns an error if evaluation fails, the array is not scalar,
318    /// or `T` does not exactly match the array dtype.
319    pub fn try_item_exact<T: ArrayElement>(&self) -> Result<T, ConversionError> {
320        if self.size() != 1 {
321            return Err(ConversionError::NotScalar {
322                actual: self.size(),
323            });
324        }
325        if self.dtype() != T::DTYPE {
326            return Err(ConversionError::DtypeMismatch {
327                expected: T::DTYPE,
328                actual: self.dtype(),
329            });
330        }
331        self.eval()?;
332        Ok(T::array_item(self)?)
333    }
334
335    /// Access the value of a scalar array, converting it to `T` when necessary.
336    ///
337    /// This evaluates the array and panics if evaluation or conversion fails or the array is not
338    /// scalar.
339    pub fn item_cast<T: ArrayElement>(&self) -> T {
340        self.try_item_cast().unwrap()
341    }
342
343    /// Access the value of a scalar array, converting it to `T` when necessary.
344    ///
345    /// This evaluates the array and returns an error if evaluation or conversion fails or the
346    /// array is not scalar.
347    pub fn try_item_cast<T: ArrayElement>(&self) -> Result<T, ConversionError> {
348        if self.size() != 1 {
349            return Err(ConversionError::NotScalar {
350                actual: self.size(),
351            });
352        }
353        self.eval()?;
354
355        if self.dtype() != T::DTYPE {
356            return self.as_type::<T>()?.try_item_exact();
357        }
358
359        Ok(T::array_item(self)?)
360    }
361
362    /// Compatibility alias for [`Array::item_cast`].
363    #[deprecated(since = "0.26.0", note = "use `item_cast` or `item_exact`")]
364    pub fn item<T: ArrayElement>(&self) -> T {
365        self.item_cast()
366    }
367
368    /// Compatibility alias for [`Array::try_item_cast`].
369    #[deprecated(since = "0.26.0", note = "use `try_item_cast` or `try_item_exact`")]
370    pub fn try_item<T: ArrayElement>(&self) -> crate::error::Result<T> {
371        self.try_item_cast().map_err(|error| match error {
372            ConversionError::Exception(error) => error,
373            error => crate::error::Exception::custom(error.to_string()),
374        })
375    }
376
377    /// Copy contiguous row-major array values without dtype conversion.
378    ///
379    /// This evaluates the array and allocates a `Vec`. It returns an error if `T` does not exactly
380    /// match the array dtype or if the array is not contiguous row-major. Call
381    /// [`Array::contiguous`] first to materialize a row-major array.
382    pub fn to_vec_exact<T: ArrayElement + Clone>(&self) -> Result<Vec<T>, ConversionError> {
383        if self.dtype() != T::DTYPE {
384            return Err(ConversionError::DtypeMismatch {
385                expected: T::DTYPE,
386                actual: self.dtype(),
387            });
388        }
389        Ok(self.try_as_slice::<T>()?.to_vec())
390    }
391
392    /// Copy contiguous row-major array values, converting them to `T` when necessary.
393    ///
394    /// This evaluates the array, may allocate a converted MLX array, and allocates a `Vec`. It
395    /// returns an error if evaluation or conversion fails or if the converted array is not
396    /// contiguous row-major. Call [`Array::contiguous`] first to materialize a row-major array.
397    pub fn to_vec_cast<T: ArrayElement + Clone>(&self) -> Result<Vec<T>, ConversionError> {
398        if self.dtype() == T::DTYPE {
399            return self.to_vec_exact();
400        }
401        let converted = self.as_type::<T>()?;
402        Ok(converted.try_as_slice::<T>()?.to_vec())
403    }
404
405    /// Returns a slice of the array data without validating the dtype.
406    ///
407    /// This evaluates the array first and panics if evaluation fails.
408    ///
409    /// # Safety
410    ///
411    /// This is unsafe because the underlying data ptr is not checked for null or if the desired
412    /// dtype matches the actual dtype of the array.
413    ///
414    /// # Example
415    ///
416    /// ```rust
417    /// use mlx_rs::Array;
418    ///
419    /// let data = [1i32, 2, 3, 4, 5];
420    /// let mut array = Array::from_slice(&data[..], &[5]);
421    ///
422    /// unsafe {
423    ///    let slice = array.as_slice_unchecked::<i32>();
424    ///    assert_eq!(slice, &[1, 2, 3, 4, 5]);
425    /// }
426    /// ```
427    pub unsafe fn as_slice_unchecked<T: ArrayElement>(&self) -> &[T] {
428        self.eval().unwrap();
429
430        unsafe {
431            let data = T::array_data(self);
432            let size = self.size();
433            std::slice::from_raw_parts(data, size)
434        }
435    }
436
437    /// Returns a slice of contiguous row-major array data.
438    ///
439    /// This evaluates the array first and returns any evaluation error. It also returns an error if
440    /// the dtype does not match or the array is a non-contiguous view; it never copies or casts to
441    /// produce the slice. Call [`Array::contiguous`] first to materialize a row-major array.
442    ///
443    /// # Example
444    ///
445    /// ```rust
446    /// use mlx_rs::Array;
447    ///
448    /// let data = [1i32, 2, 3, 4, 5];
449    /// let mut array = Array::from_slice(&data[..], &[5]);
450    ///
451    /// let slice = array.try_as_slice::<i32>();
452    /// assert_eq!(slice, Ok(&data[..]));
453    /// ```
454    pub fn try_as_slice<T: ArrayElement>(&self) -> Result<&[T], AsSliceError> {
455        if self.dtype() != T::DTYPE {
456            return Err(AsSliceError::DtypeMismatch {
457                expecting: T::DTYPE,
458                found: self.dtype(),
459            });
460        }
461
462        self.eval()?;
463
464        let row_contiguous = bool::try_from_op(|res| unsafe {
465            mlx_sys::_mlx_array_is_row_contiguous(res, self.as_ptr())
466        })?;
467        if !row_contiguous {
468            return Err(AsSliceError::NotContiguous);
469        }
470
471        unsafe {
472            let size = self.size();
473            let data = T::array_data(self);
474            if data.is_null() || size == 0 {
475                return Err(AsSliceError::Null);
476            }
477
478            Ok(std::slice::from_raw_parts(data, size))
479        }
480    }
481
482    /// Returns a slice of contiguous row-major array data, evaluating the array first.
483    ///
484    /// # Panics
485    ///
486    /// Panics if evaluation fails, the desired dtype does not match the actual dtype, or the array
487    /// is not contiguous row-major. Call [`Array::contiguous`] first for non-contiguous views.
488    ///
489    /// # Example
490    ///
491    /// ```rust
492    /// use mlx_rs::Array;
493    ///
494    /// let data = [1i32, 2, 3, 4, 5];
495    /// let mut array = Array::from_slice(&data[..], &[5]);
496    ///
497    /// let slice = array.as_slice::<i32>();
498    /// assert_eq!(slice, &data[..]);
499    /// ```
500    pub fn as_slice<T: ArrayElement>(&self) -> &[T] {
501        self.try_as_slice().unwrap()
502    }
503
504    /// Clone the array by copying the data.
505    ///
506    /// This is named `deep_clone` to avoid confusion with the `Clone` trait.
507    pub fn deep_clone(&self) -> Self {
508        unsafe {
509            let dtype = self.dtype();
510            let shape = self.shape();
511            let data = match dtype {
512                Dtype::Bool => mlx_sys::mlx_array_data_bool(self.as_ptr()) as *const c_void,
513                Dtype::Uint8 => mlx_sys::mlx_array_data_uint8(self.as_ptr()) as *const c_void,
514                Dtype::Uint16 => mlx_sys::mlx_array_data_uint16(self.as_ptr()) as *const c_void,
515                Dtype::Uint32 => mlx_sys::mlx_array_data_uint32(self.as_ptr()) as *const c_void,
516                Dtype::Uint64 => mlx_sys::mlx_array_data_uint64(self.as_ptr()) as *const c_void,
517                Dtype::Int8 => mlx_sys::mlx_array_data_int8(self.as_ptr()) as *const c_void,
518                Dtype::Int16 => mlx_sys::mlx_array_data_int16(self.as_ptr()) as *const c_void,
519                Dtype::Int32 => mlx_sys::mlx_array_data_int32(self.as_ptr()) as *const c_void,
520                Dtype::Int64 => mlx_sys::mlx_array_data_int64(self.as_ptr()) as *const c_void,
521                Dtype::Float16 => mlx_sys::mlx_array_data_float16(self.as_ptr()) as *const c_void,
522                Dtype::Float32 => mlx_sys::mlx_array_data_float32(self.as_ptr()) as *const c_void,
523                Dtype::Float64 => mlx_sys::mlx_array_data_float64(self.as_ptr()) as *const c_void,
524                Dtype::Bfloat16 => mlx_sys::mlx_array_data_bfloat16(self.as_ptr()) as *const c_void,
525                Dtype::Complex64 => {
526                    mlx_sys::mlx_array_data_complex64(self.as_ptr()) as *const c_void
527                }
528            };
529
530            let new_c_array =
531                mlx_sys::mlx_array_new_data(data, shape.as_ptr(), shape.len() as i32, dtype.into());
532
533            Array::from_ptr(new_c_array)
534        }
535    }
536}
537
538impl Clone for Array {
539    fn clone(&self) -> Self {
540        Array::try_from_op(|res| unsafe { mlx_sys::mlx_array_set(res, self.as_ptr()) })
541            // Exception may be thrown when calling `new` in cpp.
542            .expect("Failed to clone array")
543    }
544}
545
546impl Sum for Array {
547    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
548        iter.fold(Array::from_int(0), |acc, x| acc.add(&x).unwrap())
549    }
550}
551
552/// Stop gradients from being computed.
553///
554/// The operation is the identity but it prevents gradients from flowing
555/// through the array.
556#[default_device]
557pub fn stop_gradient_device(
558    a: impl AsRef<Array>,
559    stream: impl AsRef<Stream>,
560) -> crate::error::Result<Array> {
561    Array::try_from_op(|res| unsafe {
562        mlx_sys::mlx_stop_gradient(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
563    })
564}
565
566impl From<bool> for Array {
567    fn from(value: bool) -> Self {
568        Array::from_bool(value)
569    }
570}
571
572impl From<i32> for Array {
573    fn from(value: i32) -> Self {
574        Array::from_int(value)
575    }
576}
577
578impl From<f32> for Array {
579    fn from(value: f32) -> Self {
580        Array::from_f32(value)
581    }
582}
583
584impl From<complex64> for Array {
585    fn from(value: complex64) -> Self {
586        Array::from_complex(value)
587    }
588}
589
590impl<T> From<T> for Array
591where
592    Array: FromNested<T>,
593{
594    fn from(value: T) -> Self {
595        Array::from_nested(value)
596    }
597}
598
599impl AsRef<Array> for Array {
600    fn as_ref(&self) -> &Array {
601        self
602    }
603}
604
605/// A helper trait to construct `Array` from scalar values.
606///
607/// This trait is intended to be used with the macro [`array!`] but can be used directly if needed.
608pub trait FromScalar<T>
609where
610    T: ArrayElement,
611{
612    /// Create an array from a scalar value.
613    fn from_scalar(val: T) -> Array;
614}
615
616impl FromScalar<bool> for Array {
617    fn from_scalar(val: bool) -> Array {
618        Array::from_bool(val)
619    }
620}
621
622impl FromScalar<i32> for Array {
623    fn from_scalar(val: i32) -> Array {
624        Array::from_int(val)
625    }
626}
627
628impl FromScalar<f32> for Array {
629    fn from_scalar(val: f32) -> Array {
630        Array::from_f32(val)
631    }
632}
633
634impl FromScalar<complex64> for Array {
635    fn from_scalar(val: complex64) -> Array {
636        Array::from_complex(val)
637    }
638}
639
640/// A helper trait to construct `Array` from nested arrays or slices.
641///
642/// Given that this is not intended for use other than the macro [`array!`], this trait is added
643/// instead of directly implementing `From` for `Array` to avoid conflicts with other `From`
644/// implementations.
645///
646/// Beware that this is subject to change in the future should we find a better way to implement
647/// the macro without creating conflicts.
648pub trait FromNested<T> {
649    /// Create an array from nested arrays or slices.
650    fn from_nested(data: T) -> Array;
651}
652
653impl<T: FromSliceElement> FromNested<&[T]> for Array {
654    fn from_nested(data: &[T]) -> Self {
655        Array::from_slice(data, &[data.len() as i32])
656    }
657}
658
659impl<T: FromSliceElement, const N: usize> FromNested<[T; N]> for Array {
660    fn from_nested(data: [T; N]) -> Self {
661        Array::from_slice(&data, &[N as i32])
662    }
663}
664
665impl<T: FromSliceElement, const N: usize> FromNested<&[T; N]> for Array {
666    fn from_nested(data: &[T; N]) -> Self {
667        Array::from_slice(data, &[N as i32])
668    }
669}
670
671impl<T: FromSliceElement + Copy> FromNested<&[&[T]]> for Array {
672    fn from_nested(data: &[&[T]]) -> Self {
673        // check that all rows have the same length
674        let row_len = data[0].len();
675        assert!(
676            data.iter().all(|row| row.len() == row_len),
677            "Rows must have the same length"
678        );
679
680        let shape = [data.len() as i32, row_len as i32];
681        let data = data
682            .iter()
683            .flat_map(|x| x.iter())
684            .copied()
685            .collect::<Vec<T>>();
686        Array::from_slice(&data, &shape)
687    }
688}
689
690impl<T: FromSliceElement + Copy, const N: usize> FromNested<[&[T]; N]> for Array {
691    fn from_nested(data: [&[T]; N]) -> Self {
692        // check that all rows have the same length
693        let row_len = data[0].len();
694        assert!(
695            data.iter().all(|row| row.len() == row_len),
696            "Rows must have the same length"
697        );
698
699        let shape = [N as i32, row_len as i32];
700        let data = data
701            .iter()
702            .flat_map(|x| x.iter())
703            .copied()
704            .collect::<Vec<T>>();
705        Array::from_slice(&data, &shape)
706    }
707}
708
709impl<T: FromSliceElement + Copy, const N: usize> FromNested<&[[T; N]]> for Array {
710    fn from_nested(data: &[[T; N]]) -> Self {
711        let shape = [data.len() as i32, N as i32];
712        let data = data
713            .iter()
714            .flat_map(|x| x.iter().copied())
715            .collect::<Vec<T>>();
716        Array::from_slice(&data, &shape)
717    }
718}
719
720impl<T: FromSliceElement + Copy, const N: usize> FromNested<&[&[T; N]]> for Array {
721    fn from_nested(data: &[&[T; N]]) -> Self {
722        let shape = [data.len() as i32, N as i32];
723        let data = data
724            .iter()
725            .flat_map(|x| x.iter().copied())
726            .collect::<Vec<T>>();
727        Array::from_slice(&data, &shape)
728    }
729}
730
731impl<T: FromSliceElement + Copy, const N: usize, const M: usize> FromNested<[[T; N]; M]> for Array {
732    fn from_nested(data: [[T; N]; M]) -> Self {
733        let shape = [M as i32, N as i32];
734        let data = data
735            .iter()
736            .flat_map(|x| x.iter().copied())
737            .collect::<Vec<T>>();
738        Array::from_slice(&data, &shape)
739    }
740}
741
742impl<T: FromSliceElement + Copy, const N: usize, const M: usize> FromNested<&[[T; N]; M]>
743    for Array
744{
745    fn from_nested(data: &[[T; N]; M]) -> Self {
746        let shape = [M as i32, N as i32];
747        let data = data
748            .iter()
749            .flat_map(|x| x.iter().copied())
750            .collect::<Vec<T>>();
751        Array::from_slice(&data, &shape)
752    }
753}
754
755impl<T: FromSliceElement + Copy, const N: usize, const M: usize> FromNested<&[&[T; N]; M]>
756    for Array
757{
758    fn from_nested(data: &[&[T; N]; M]) -> Self {
759        let shape = [M as i32, N as i32];
760        let data = data
761            .iter()
762            .flat_map(|x| x.iter().copied())
763            .collect::<Vec<T>>();
764        Array::from_slice(&data, &shape)
765    }
766}
767
768impl<T: FromSliceElement + Copy> FromNested<&[&[&[T]]]> for Array {
769    fn from_nested(data: &[&[&[T]]]) -> Self {
770        // check that 2nd dimension has the same length
771        let len_2d = data[0].len();
772        assert!(
773            data.iter().all(|x| x.len() == len_2d),
774            "2nd dimension must have the same length"
775        );
776
777        // check that 3rd dimension has the same length
778        let len_3d = data[0][0].len();
779        assert!(
780            data.iter().all(|x| x.iter().all(|y| y.len() == len_3d)),
781            "3rd dimension must have the same length"
782        );
783
784        let shape = [data.len() as i32, len_2d as i32, len_3d as i32];
785        let data = data
786            .iter()
787            .flat_map(|x| x.iter().flat_map(|y| y.iter().copied()))
788            .collect::<Vec<T>>();
789        Array::from_slice(&data, &shape)
790    }
791}
792
793impl<T: FromSliceElement + Copy, const N: usize> FromNested<[&[&[T]]; N]> for Array {
794    fn from_nested(data: [&[&[T]]; N]) -> Self {
795        // check that 2nd dimension has the same length
796        let len_2d = data[0].len();
797        assert!(
798            data.iter().all(|x| x.len() == len_2d),
799            "2nd dimension must have the same length"
800        );
801
802        // check that 3rd dimension has the same length
803        let len_3d = data[0][0].len();
804        assert!(
805            data.iter().all(|x| x.iter().all(|y| y.len() == len_3d)),
806            "3rd dimension must have the same length"
807        );
808
809        let shape = [N as i32, len_2d as i32, len_3d as i32];
810        let data = data
811            .iter()
812            .flat_map(|x| x.iter().flat_map(|y| y.iter().copied()))
813            .collect::<Vec<T>>();
814        Array::from_slice(&data, &shape)
815    }
816}
817
818impl<T: FromSliceElement + Copy, const N: usize> FromNested<&[[&[T]; N]]> for Array {
819    fn from_nested(data: &[[&[T]; N]]) -> Self {
820        // check that 3rd dimension has the same length
821        let len_3d = data[0][0].len();
822        assert!(
823            data.iter().all(|x| x.iter().all(|y| y.len() == len_3d)),
824            "3rd dimension must have the same length"
825        );
826
827        let shape = [data.len() as i32, N as i32, len_3d as i32];
828        let data = data
829            .iter()
830            .flat_map(|x| x.iter().flat_map(|y| y.iter().copied()))
831            .collect::<Vec<T>>();
832        Array::from_slice(&data, &shape)
833    }
834}
835
836impl<T: FromSliceElement + Copy, const N: usize> FromNested<&[&[[T; N]]]> for Array {
837    fn from_nested(data: &[&[[T; N]]]) -> Self {
838        // check that 2nd dimension has the same length
839        let len_2d = data[0].len();
840        assert!(
841            data.iter().all(|x| x.len() == len_2d),
842            "2nd dimension must have the same length"
843        );
844
845        let shape = [data.len() as i32, len_2d as i32, N as i32];
846        let data = data
847            .iter()
848            .flat_map(|x| x.iter().flat_map(|y| y.iter().copied()))
849            .collect::<Vec<T>>();
850        Array::from_slice(&data, &shape)
851    }
852}
853
854impl<T: FromSliceElement + Copy, const N: usize, const M: usize> FromNested<[[&[T]; N]; M]>
855    for Array
856{
857    fn from_nested(data: [[&[T]; N]; M]) -> Self {
858        // check that 3rd dimension has the same length
859        let len_3d = data[0][0].len();
860        assert!(
861            data.iter().all(|x| x.iter().all(|y| y.len() == len_3d)),
862            "3rd dimension must have the same length"
863        );
864
865        let shape = [M as i32, N as i32, len_3d as i32];
866        let data = data
867            .iter()
868            .flat_map(|x| x.iter().flat_map(|y| y.iter().copied()))
869            .collect::<Vec<T>>();
870        Array::from_slice(&data, &shape)
871    }
872}
873
874impl<T: FromSliceElement + Copy, const N: usize, const M: usize> FromNested<&[[&[T]; N]; M]>
875    for Array
876{
877    fn from_nested(data: &[[&[T]; N]; M]) -> Self {
878        // check that 3rd dimension has the same length
879        let len_3d = data[0][0].len();
880        assert!(
881            data.iter().all(|x| x.iter().all(|y| y.len() == len_3d)),
882            "3rd dimension must have the same length"
883        );
884
885        let shape = [M as i32, N as i32, len_3d as i32];
886        let data = data
887            .iter()
888            .flat_map(|x| x.iter().flat_map(|y| y.iter().copied()))
889            .collect::<Vec<T>>();
890        Array::from_slice(&data, &shape)
891    }
892}
893
894impl<T: FromSliceElement + Copy, const N: usize, const M: usize> FromNested<&[&[[T; N]]; M]>
895    for Array
896{
897    fn from_nested(data: &[&[[T; N]]; M]) -> Self {
898        // check that 2nd dimension has the same length
899        let len_2d = data[0].len();
900        assert!(
901            data.iter().all(|x| x.len() == len_2d),
902            "2nd dimension must have the same length"
903        );
904
905        let shape = [M as i32, len_2d as i32, N as i32];
906        let data = data
907            .iter()
908            .flat_map(|x| x.iter().flat_map(|y| y.iter().copied()))
909            .collect::<Vec<T>>();
910        Array::from_slice(&data, &shape)
911    }
912}
913
914impl<T: FromSliceElement + Copy, const N: usize, const M: usize, const O: usize>
915    FromNested<[[[T; N]; M]; O]> for Array
916{
917    fn from_nested(data: [[[T; N]; M]; O]) -> Self {
918        let shape = [O as i32, M as i32, N as i32];
919        let data = data
920            .iter()
921            .flat_map(|x| x.iter().flat_map(|y| y.iter().copied()))
922            .collect::<Vec<T>>();
923        Array::from_slice(&data, &shape)
924    }
925}
926
927impl<T: FromSliceElement + Copy, const N: usize, const M: usize, const O: usize>
928    FromNested<&[[[T; N]; M]; O]> for Array
929{
930    fn from_nested(data: &[[[T; N]; M]; O]) -> Self {
931        let shape = [O as i32, M as i32, N as i32];
932        let data = data
933            .iter()
934            .flat_map(|x| x.iter().flat_map(|y| y.iter().copied()))
935            .collect::<Vec<T>>();
936        Array::from_slice(&data, &shape)
937    }
938}
939
940impl<T: FromSliceElement + Copy, const N: usize, const M: usize, const O: usize>
941    FromNested<&[&[[T; N]; M]; O]> for Array
942{
943    fn from_nested(data: &[&[[T; N]; M]; O]) -> Self {
944        let shape = [O as i32, M as i32, N as i32];
945        let data = data
946            .iter()
947            .flat_map(|x| x.iter().flat_map(|y| y.iter().copied()))
948            .collect::<Vec<T>>();
949        Array::from_slice(&data, &shape)
950    }
951}
952
953impl<T: FromSliceElement + Copy, const N: usize, const M: usize, const O: usize>
954    FromNested<&[[&[T; N]; M]; O]> for Array
955{
956    fn from_nested(data: &[[&[T; N]; M]; O]) -> Self {
957        let shape = [O as i32, M as i32, N as i32];
958        let data = data
959            .iter()
960            .flat_map(|x| x.iter().flat_map(|y| y.iter().copied()))
961            .collect::<Vec<T>>();
962        Array::from_slice(&data, &shape)
963    }
964}
965
966impl<T: FromSliceElement + Copy, const N: usize, const M: usize, const O: usize>
967    FromNested<&[&[&[T; N]; M]; O]> for Array
968{
969    fn from_nested(data: &[&[&[T; N]; M]; O]) -> Self {
970        let shape = [O as i32, M as i32, N as i32];
971        let data = data
972            .iter()
973            .flat_map(|x| x.iter().flat_map(|y| y.iter().copied()))
974            .collect::<Vec<T>>();
975        Array::from_slice(&data, &shape)
976    }
977}
978
979#[cfg(test)]
980mod tests {
981    use super::*;
982    use crate::{array, ops::broadcast_to};
983
984    #[test]
985    fn broadcast_view_cannot_be_borrowed_as_slice() {
986        let broadcast = broadcast_to(&array!([1, 2]), &[3, 2]).unwrap();
987
988        assert_eq!(
989            broadcast.try_as_slice::<i32>(),
990            Err(AsSliceError::NotContiguous)
991        );
992    }
993
994    #[test]
995    fn new_scalar_array_from_bool() {
996        let array = Array::from_bool(true);
997        assert!(array.item_exact::<bool>());
998        assert_eq!(array.item_size(), 1);
999        assert_eq!(array.size(), 1);
1000        assert!(array.strides().is_empty());
1001        assert_eq!(array.nbytes(), 1);
1002        assert_eq!(array.ndim(), 0);
1003        assert!(array.shape().is_empty());
1004        assert_eq!(array.dtype(), Dtype::Bool);
1005    }
1006
1007    #[test]
1008    fn new_scalar_array_from_int() {
1009        let array = Array::from_int(42);
1010        assert_eq!(array.item_exact::<i32>(), 42);
1011        assert_eq!(array.item_size(), 4);
1012        assert_eq!(array.size(), 1);
1013        assert!(array.strides().is_empty());
1014        assert_eq!(array.nbytes(), 4);
1015        assert_eq!(array.ndim(), 0);
1016        assert!(array.shape().is_empty());
1017        assert_eq!(array.dtype(), Dtype::Int32);
1018    }
1019
1020    #[test]
1021    fn new_scalar_array_from_f32() {
1022        let array = Array::from_f32(3.14);
1023        assert_eq!(array.item_exact::<f32>(), 3.14);
1024        assert_eq!(array.item_size(), 4);
1025        assert_eq!(array.size(), 1);
1026        assert!(array.strides().is_empty());
1027        assert_eq!(array.nbytes(), 4);
1028        assert_eq!(array.ndim(), 0);
1029        assert!(array.shape().is_empty());
1030        assert_eq!(array.dtype(), Dtype::Float32);
1031    }
1032
1033    #[test]
1034    fn new_scalar_array_from_f64() {
1035        let array = Array::from_f64(3.14).as_dtype(Dtype::Float64).unwrap();
1036        float_eq::assert_float_eq!(array.item_exact::<f64>(), 3.14, abs <= 1e-5);
1037        assert_eq!(array.item_size(), 8);
1038        assert_eq!(array.size(), 1);
1039        assert!(array.strides().is_empty());
1040        assert_eq!(array.nbytes(), 8);
1041        assert_eq!(array.ndim(), 0);
1042        assert!(array.shape().is_empty());
1043        assert_eq!(array.dtype(), Dtype::Float64);
1044    }
1045
1046    #[test]
1047    fn new_array_from_slice_f64() {
1048        let array = Array::from_slice_f64(&[1.0, 2.0, 3.0], &[3]);
1049        assert_eq!(array.item_size(), 8);
1050        assert_eq!(array.size(), 3);
1051        assert_eq!(array.strides(), &[1]);
1052        assert_eq!(array.nbytes(), 24);
1053        assert_eq!(array.ndim(), 1);
1054        assert_eq!(array.dim(0), 3);
1055        assert_eq!(array.shape(), &[3]);
1056        assert_eq!(array.dtype(), Dtype::Float64);
1057    }
1058
1059    #[test]
1060    fn new_scalar_array_from_complex() {
1061        let val = complex64::new(1.0, 2.0);
1062        let array = Array::from_complex(val);
1063        assert_eq!(array.item_exact::<complex64>(), val);
1064        assert_eq!(array.item_size(), 8);
1065        assert_eq!(array.size(), 1);
1066        assert!(array.strides().is_empty());
1067        assert_eq!(array.nbytes(), 8);
1068        assert_eq!(array.ndim(), 0);
1069        assert!(array.shape().is_empty());
1070        assert_eq!(array.dtype(), Dtype::Complex64);
1071    }
1072
1073    #[test]
1074    fn new_array_from_single_element_slice() {
1075        let data = [1i32];
1076        let array = Array::from_slice(&data, &[1]);
1077        assert_eq!(array.as_slice::<i32>(), &data[..]);
1078        assert_eq!(array.item_exact::<i32>(), 1);
1079        assert_eq!(array.item_size(), 4);
1080        assert_eq!(array.size(), 1);
1081        assert_eq!(array.strides(), &[1]);
1082        assert_eq!(array.nbytes(), 4);
1083        assert_eq!(array.ndim(), 1);
1084        assert_eq!(array.dim(0), 1);
1085        assert_eq!(array.shape(), &[1]);
1086        assert_eq!(array.dtype(), Dtype::Int32);
1087    }
1088
1089    #[test]
1090    fn new_array_from_multi_element_slice() {
1091        let data = [1i32, 2, 3, 4, 5];
1092        let array = Array::from_slice(&data, &[5]);
1093        assert_eq!(array.as_slice::<i32>(), &data[..]);
1094        assert_eq!(array.item_size(), 4);
1095        assert_eq!(array.size(), 5);
1096        assert_eq!(array.strides(), &[1]);
1097        assert_eq!(array.nbytes(), 20);
1098        assert_eq!(array.ndim(), 1);
1099        assert_eq!(array.dim(0), 5);
1100        assert_eq!(array.shape(), &[5]);
1101        assert_eq!(array.dtype(), Dtype::Int32);
1102    }
1103
1104    #[test]
1105    fn new_2d_array_from_slice() {
1106        let data = [1i32, 2, 3, 4, 5, 6];
1107        let array = Array::from_slice(&data, &[2, 3]);
1108        assert_eq!(array.as_slice::<i32>(), &data[..]);
1109        assert_eq!(array.item_size(), 4);
1110        assert_eq!(array.size(), 6);
1111        assert_eq!(array.strides(), &[3, 1]);
1112        assert_eq!(array.nbytes(), 24);
1113        assert_eq!(array.ndim(), 2);
1114        assert_eq!(array.dim(0), 2);
1115        assert_eq!(array.dim(1), 3);
1116        assert_eq!(array.dim(-1), 3); // negative index
1117        assert_eq!(array.dim(-2), 2); // negative index
1118        assert_eq!(array.shape(), &[2, 3]);
1119        assert_eq!(array.dtype(), Dtype::Int32);
1120    }
1121
1122    #[test]
1123    fn deep_cloned_array_has_different_ptr() {
1124        let data = [1i32, 2, 3, 4, 5];
1125        let orig = Array::from_slice(&data, &[5]);
1126        let clone = orig.deep_clone();
1127
1128        // Data should be the same
1129        assert_eq!(orig.as_slice::<i32>(), clone.as_slice::<i32>());
1130
1131        // Addr of `mlx_array` should be different
1132        assert_ne!(orig.as_ptr().ctx, clone.as_ptr().ctx);
1133
1134        // Addr of data should be different
1135        assert_ne!(
1136            orig.as_slice::<i32>().as_ptr(),
1137            clone.as_slice::<i32>().as_ptr()
1138        );
1139    }
1140
1141    #[test]
1142    fn test_array_eq() {
1143        let data = [1i32, 2, 3, 4, 5];
1144        let array1 = Array::from_slice(&data, &[5]);
1145        let array2 = Array::from_slice(&data, &[5]);
1146        let array3 = Array::from_slice(&[1i32, 2, 3, 4, 6], &[5]);
1147
1148        assert!(array1.eq_exact(&array2).unwrap());
1149        assert!(!array1.eq_exact(&array3).unwrap());
1150    }
1151
1152    #[test]
1153    fn test_array_item_non_scalar() {
1154        let data = [1i32, 2, 3, 4, 5];
1155        let array = Array::from_slice(&data, &[5]);
1156        assert!(array.try_item_exact::<i32>().is_err());
1157    }
1158
1159    #[test]
1160    fn test_item_type_conversion() {
1161        let array = Array::from_f32(1.0);
1162        assert_eq!(array.item_cast::<i32>(), 1);
1163        assert_eq!(array.item_cast::<complex64>(), complex64::new(1.0, 0.0));
1164        assert_eq!(array.item_cast::<u8>(), 1);
1165
1166        assert_eq!(array.as_slice::<f32>(), &[1.0]);
1167    }
1168
1169    #[test]
1170    fn exact_observers_reject_dtype_conversion() {
1171        crate::with_device(crate::Device::cpu(), || {
1172            let array = Array::from_slice(&[1_i32, 2, 3], &[3]);
1173            let scalar = Array::from_int(1);
1174
1175            let vector_error = array.to_vec_exact::<f32>().unwrap_err();
1176            assert!(
1177                matches!(
1178                    vector_error,
1179                    crate::error::ConversionError::DtypeMismatch {
1180                        expected: Dtype::Float32,
1181                        actual: Dtype::Int32,
1182                    }
1183                ),
1184                "{vector_error:?}; dtype={:?}",
1185                array.dtype()
1186            );
1187            assert!(matches!(
1188                scalar.try_item_exact::<f32>(),
1189                Err(crate::error::ConversionError::DtypeMismatch {
1190                    expected: Dtype::Float32,
1191                    actual: Dtype::Int32,
1192                })
1193            ));
1194        });
1195    }
1196
1197    #[test]
1198    fn casting_observers_convert_values() {
1199        crate::with_device(crate::Device::cpu(), || {
1200            let vector = Array::from_slice(&[1_i32, 2, 3], &[3]);
1201            let scalar = Array::from_int(7);
1202
1203            assert_eq!(vector.to_vec_cast::<f32>().unwrap(), vec![1.0, 2.0, 3.0]);
1204            assert_eq!(scalar.try_item_cast::<f32>().unwrap(), 7.0);
1205        });
1206    }
1207
1208    #[test]
1209    fn explicit_array_comparisons_distinguish_dtype_and_values() {
1210        crate::with_device(crate::Device::cpu(), || {
1211            let integers = Array::from_slice(&[1_i32, 2, 3], &[3]);
1212            let same_integers = Array::from_slice(&[1_i32, 2, 3], &[3]);
1213            let floats = Array::from_slice(&[1.0_f32, 2.0, 3.0], &[3]);
1214            let nearby = Array::from_slice(&[1.0_f32, 2.0, 3.000_001], &[3]);
1215
1216            assert!(integers.eq_exact(&same_integers).unwrap());
1217            assert!(!integers.eq_exact(&floats).unwrap());
1218            assert!(integers.eq_values(&floats).unwrap());
1219            assert!(floats
1220                .all_close(&nearby, Some(1e-5), Some(1e-8), Some(false))
1221                .unwrap());
1222        });
1223    }
1224}