numrs/
array_view.rs

1//! ArrayView - Zero-copy view into typed data for FFI operations
2//!
3//! Este struct resuelve el problema de performance en FFI:
4//! - El caller mantiene ownership de los datos en C/Python/JS
5//! - ArrayView contiene solo referencias (slices) - NO copia datos
6//! - Compatible con dispatch system existente
7//!
8//! # Arquitectura
9//! Para FFI, usamos un patrón de "view handle":
10//! 1. C API recibe void* ptr del caller (datos en memoria C)
11//! 2. Rust crea ArrayView con slice::from_raw_parts (sin to_vec!)
12//! 3. ArrayView se guarda en Box y retorna opaque pointer al caller
13//! 4. Caller usa ese handle para múltiples operaciones
14//! 5. Caller destruye el handle cuando termina
15
16use crate::array::DType;
17
18/// View into typed array data - wraps borrowed slices (no copies!)
19///
20/// # Diseño
21/// Para evitar to_vec() completamente, ArrayView contiene referencias
22/// con lifetime 'static (seguro porque el caller garantiza que los datos
23/// viven mientras el view existe - via Box en C API).
24///
25/// # Uso en FFI
26/// ```ignore
27/// // C tiene: float* data_a, float* data_b (10000 elementos cada uno)
28/// 
29/// // Crear views (sin to_vec! - solo crea slice):
30/// let view_a = unsafe { ArrayView::from_ptr_f32(data_a, 10000) };
31/// let view_b = unsafe { ArrayView::from_ptr_f32(data_b, 10000) };
32/// 
33/// // Múltiples operaciones - todas zero-copy:
34/// ops_inplace::elementwise_view(&view_a, &view_b, out, Add)?;  // ~23μs
35/// ops_inplace::elementwise_view(&view_a, &view_b, out, Mul)?;  // ~23μs
36/// ops_inplace::elementwise_view(&view_a, &view_b, out, Sub)?;  // ~23μs
37/// // Sin to_vec(), cada operación corre a velocidad nativa!
38/// ```
39#[derive(Debug, Clone)]
40pub enum ArrayView {
41    F32(&'static [f32]),
42    F64(&'static [f64]),
43    I32(&'static [i32]),
44    I64(&'static [i64]),
45    U8(&'static [u8]),
46}
47
48impl ArrayView {
49    /// Create from f32 slice (zero-copy via raw pointer)
50    /// 
51    /// # Safety
52    /// Caller must ensure data outlives the ArrayView. For FFI, this is safe
53    /// because ArrayView is boxed and C API controls lifetime.
54    pub fn from_slice_f32(data: &[f32]) -> Self {
55        unsafe { Self::from_ptr_f32(data.as_ptr(), data.len()) }
56    }
57    
58    /// Create from f64 slice (zero-copy via raw pointer)
59    pub fn from_slice_f64(data: &[f64]) -> Self {
60        unsafe { Self::from_ptr_f64(data.as_ptr(), data.len()) }
61    }
62    
63    /// Create from i32 slice (zero-copy via raw pointer)
64    pub fn from_slice_i32(data: &[i32]) -> Self {
65        unsafe { Self::from_ptr_i32(data.as_ptr(), data.len()) }
66    }
67    
68    /// Create from raw pointer (unsafe - for FFI)
69    /// 
70    /// # Safety
71    /// - `ptr` must be valid for `len` elements
72    /// - Data must outlive the ArrayView (caller's responsibility in C API via Box)
73    /// - 'static lifetime is safe because C API holds Box<ArrayView> until destroy()
74    pub unsafe fn from_ptr_f32(ptr: *const f32, len: usize) -> Self {
75        ArrayView::F32(std::slice::from_raw_parts(ptr, len))
76    }
77    
78    /// Create from raw pointer (unsafe - for FFI)
79    pub unsafe fn from_ptr_f64(ptr: *const f64, len: usize) -> Self {
80        ArrayView::F64(std::slice::from_raw_parts(ptr, len))
81    }
82    
83    /// Create from raw pointer (unsafe - for FFI)
84    pub unsafe fn from_ptr_i32(ptr: *const i32, len: usize) -> Self {
85        ArrayView::I32(std::slice::from_raw_parts(ptr, len))
86    }
87    
88    /// Get dtype
89    pub fn dtype(&self) -> DType {
90        match self {
91            ArrayView::F32(_) => DType::F32,
92            ArrayView::F64(_) => DType::F64,
93            ArrayView::I32(_) => DType::I32,
94            ArrayView::I64(_) => DType::I32, // Fallback to I32 for now
95            ArrayView::U8(_) => DType::U8,
96        }
97    }
98    
99    /// Get length
100    pub fn len(&self) -> usize {
101        match self {
102            ArrayView::F32(v) => v.len(),
103            ArrayView::F64(v) => v.len(),
104            ArrayView::I32(v) => v.len(),
105            ArrayView::I64(v) => v.len(),
106            ArrayView::U8(v) => v.len(),
107        }
108    }
109    
110    /// Check if empty
111    pub fn is_empty(&self) -> bool {
112        self.len() == 0
113    }
114    
115    /// Get as f32 slice (zero-copy)
116    pub fn as_f32(&self) -> Option<&[f32]> {
117        match self {
118            ArrayView::F32(v) => Some(v),
119            _ => None,
120        }
121    }
122    
123    /// Get as f64 slice (zero-copy)
124    pub fn as_f64(&self) -> Option<&[f64]> {
125        match self {
126            ArrayView::F64(v) => Some(v),
127            _ => None,
128        }
129    }
130    
131    /// Get as i32 slice (zero-copy)
132    pub fn as_i32(&self) -> Option<&[i32]> {
133        match self {
134            ArrayView::I32(v) => Some(v),
135            _ => None,
136        }
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    
144    #[test]
145    fn test_array_view_f32() {
146        // Leak data to get 'static lifetime (ok for tests)
147        let data: &'static [f32] = Box::leak(vec![1.0f32, 2.0, 3.0].into_boxed_slice());
148        let view = ArrayView::from_slice_f32(data);
149        
150        assert_eq!(view.len(), 3);
151        assert_eq!(view.dtype(), DType::F32);
152        assert_eq!(view.as_f32().unwrap(), &[1.0, 2.0, 3.0]);
153    }
154    
155    #[test]
156    fn test_zero_copy_reference() {
157        let data: &'static [f32] = Box::leak(vec![1.0f32; 10000].into_boxed_slice());
158        let view = ArrayView::from_slice_f32(data);
159        
160        // Multiple calls to as_f32() return SAME pointer - true zero-copy
161        let slice1 = view.as_f32().unwrap();
162        let slice2 = view.as_f32().unwrap();
163        
164        assert_eq!(slice1.as_ptr(), slice2.as_ptr());
165        assert_eq!(slice1.as_ptr(), data.as_ptr()); // Points to original data!
166    }
167    
168    #[test]
169    fn test_from_ptr_zero_copy() {
170        let data = vec![1.0f32, 2.0, 3.0, 4.0, 5.0];
171        let ptr = data.as_ptr();
172        let len = data.len();
173        
174        unsafe {
175            let view = ArrayView::from_ptr_f32(ptr, len);
176            
177            // View apunta directamente a data - no hay to_vec()
178            assert_eq!(view.as_f32().unwrap().as_ptr(), ptr);
179            assert_eq!(view.len(), len);
180        }
181        
182        // Keep data alive
183        drop(data);
184    }
185}