1use std::os::raw::c_void;
5
6use crate::error::check_status;
7use crate::raw;
8use crate::types::maps::{MlxMapStringToArray, MlxMapStringToString};
9use crate::types::vectors::MlxVectorString;
10use crate::{MlxArray, MlxCError, MlxStream, MlxString};
11
12fn path_argument(path: &str, operation: &'static str) -> Result<std::ffi::CString, MlxCError> {
14 std::ffi::CString::new(path).map_err(|_| MlxCError {
15 operation,
16 description: "path contains an interior null byte".to_owned(),
17 })
18}
19
20impl MlxArray {
21 pub fn load_from_file(path: &str, stream: &MlxStream) -> Result<Self, MlxCError> {
26 let operation: &'static str = "load an MLX array from a file";
27 let path_value = path_argument(path, operation)?;
28 let mut output = Self::empty();
29 let status = unsafe { raw::mlx_load(output.raw_mut(), path_value.as_ptr(), stream.raw()) };
32 check_status(status, operation)?;
33 output.require_populated(operation)?;
34 Ok(output)
35 }
36
37 pub fn save_to_file(&self, path: &str) -> Result<(), MlxCError> {
42 let operation: &'static str = "save an MLX array to a file";
43 let path_value = path_argument(path, operation)?;
44 let status = unsafe { raw::mlx_save(path_value.as_ptr(), self.raw()) };
47 check_status(status, operation)
48 }
49}
50
51pub fn load_safetensors(
57 path: &str,
58 stream: &MlxStream,
59) -> Result<(MlxMapStringToArray, MlxMapStringToString), MlxCError> {
60 let operation: &'static str = "load MLX safetensors maps from a file";
61 let path_value = path_argument(path, operation)?;
62 let mut arrays = MlxMapStringToArray::new()?;
63 let mut metadata = MlxMapStringToString::new()?;
64 let status = unsafe {
67 raw::mlx_load_safetensors(
68 arrays.raw_mut(),
69 metadata.raw_mut(),
70 path_value.as_ptr(),
71 stream.raw(),
72 )
73 };
74 check_status(status, operation)?;
75 Ok((arrays, metadata))
76}
77
78pub fn save_safetensors(
83 path: &str,
84 arrays: &MlxMapStringToArray,
85 metadata: &MlxMapStringToString,
86) -> Result<(), MlxCError> {
87 let operation: &'static str = "save MLX safetensors maps to a file";
88 let path_value = path_argument(path, operation)?;
89 let status =
91 unsafe { raw::mlx_save_safetensors(path_value.as_ptr(), arrays.raw(), metadata.raw()) };
92 check_status(status, operation)
93}
94
95#[derive(Debug)]
101pub struct MlxIoReader {
102 raw_reader: raw::mlx_io_reader,
103}
104
105impl MlxIoReader {
106 pub unsafe fn from_parts(
114 descriptor: *mut c_void,
115 vtable: raw::mlx_io_vtable,
116 ) -> Result<Self, MlxCError> {
117 let operation: &'static str = "create an MLX reader transport";
118 let raw_reader = unsafe { raw::mlx_io_reader_new(descriptor, vtable) };
121 if raw_reader.ctx.is_null() {
122 return Err(MlxCError {
123 operation,
124 description: "MLX returned an empty reader handle".to_owned(),
125 });
126 }
127 Ok(Self { raw_reader })
128 }
129
130 pub fn descriptor(&self) -> Result<*mut c_void, MlxCError> {
132 let operation: &'static str = "read the MLX reader descriptor";
133 let mut descriptor: *mut c_void = std::ptr::null_mut();
134 let status = unsafe { raw::mlx_io_reader_descriptor(&mut descriptor, self.raw_reader) };
137 check_status(status, operation)?;
138 Ok(descriptor)
139 }
140
141 pub fn to_string_lossy(&self) -> Result<String, MlxCError> {
143 let operation: &'static str = "describe an MLX reader transport";
144 let mut output = MlxString::empty();
145 let status = unsafe { raw::mlx_io_reader_tostring(output.raw_mut(), self.raw_reader) };
148 check_status(status, operation)?;
149 output.require_populated(operation)?;
150 Ok(output.to_string_lossy())
151 }
152
153 pub(crate) const fn raw(&self) -> raw::mlx_io_reader {
154 self.raw_reader
155 }
156}
157
158impl Drop for MlxIoReader {
159 fn drop(&mut self) {
160 unsafe {
163 raw::mlx_io_reader_free(self.raw_reader);
164 }
165 }
166}
167
168#[derive(Debug)]
170pub struct MlxIoWriter {
171 raw_writer: raw::mlx_io_writer,
172}
173
174impl MlxIoWriter {
175 pub unsafe fn from_parts(
183 descriptor: *mut c_void,
184 vtable: raw::mlx_io_vtable,
185 ) -> Result<Self, MlxCError> {
186 let operation: &'static str = "create an MLX writer transport";
187 let raw_writer = unsafe { raw::mlx_io_writer_new(descriptor, vtable) };
190 if raw_writer.ctx.is_null() {
191 return Err(MlxCError {
192 operation,
193 description: "MLX returned an empty writer handle".to_owned(),
194 });
195 }
196 Ok(Self { raw_writer })
197 }
198
199 pub fn descriptor(&self) -> Result<*mut c_void, MlxCError> {
201 let operation: &'static str = "read the MLX writer descriptor";
202 let mut descriptor: *mut c_void = std::ptr::null_mut();
203 let status = unsafe { raw::mlx_io_writer_descriptor(&mut descriptor, self.raw_writer) };
206 check_status(status, operation)?;
207 Ok(descriptor)
208 }
209
210 pub fn to_string_lossy(&self) -> Result<String, MlxCError> {
212 let operation: &'static str = "describe an MLX writer transport";
213 let mut output = MlxString::empty();
214 let status = unsafe { raw::mlx_io_writer_tostring(output.raw_mut(), self.raw_writer) };
217 check_status(status, operation)?;
218 output.require_populated(operation)?;
219 Ok(output.to_string_lossy())
220 }
221
222 pub(crate) const fn raw(&self) -> raw::mlx_io_writer {
223 self.raw_writer
224 }
225}
226
227impl Drop for MlxIoWriter {
228 fn drop(&mut self) {
229 unsafe {
232 raw::mlx_io_writer_free(self.raw_writer);
233 }
234 }
235}
236
237pub fn load_array_from_reader(
242 reader: &MlxIoReader,
243 stream: &MlxStream,
244) -> Result<MlxArray, MlxCError> {
245 let operation: &'static str = "load an MLX array from a reader";
246 let mut output = MlxArray::empty();
247 let status = unsafe { raw::mlx_load_reader(output.raw_mut(), reader.raw(), stream.raw()) };
250 check_status(status, operation)?;
251 output.require_populated(operation)?;
252 Ok(output)
253}
254
255pub fn load_safetensors_from_reader(
260 reader: &MlxIoReader,
261 stream: &MlxStream,
262) -> Result<(MlxMapStringToArray, MlxMapStringToString), MlxCError> {
263 let operation: &'static str = "load MLX safetensors maps from a reader";
264 let mut arrays = MlxMapStringToArray::new()?;
265 let mut metadata = MlxMapStringToString::new()?;
266 let status = unsafe {
269 raw::mlx_load_safetensors_reader(
270 arrays.raw_mut(),
271 metadata.raw_mut(),
272 reader.raw(),
273 stream.raw(),
274 )
275 };
276 check_status(status, operation)?;
277 Ok((arrays, metadata))
278}
279
280pub fn save_array_to_writer(writer: &MlxIoWriter, array: &MlxArray) -> Result<(), MlxCError> {
285 let operation: &'static str = "save an MLX array through a writer";
286 let status = unsafe { raw::mlx_save_writer(writer.raw(), array.raw()) };
288 check_status(status, operation)
289}
290
291pub fn save_safetensors_to_writer(
296 writer: &MlxIoWriter,
297 arrays: &MlxMapStringToArray,
298 metadata: &MlxMapStringToString,
299) -> Result<(), MlxCError> {
300 let operation: &'static str = "save MLX safetensors maps through a writer";
301 let status =
303 unsafe { raw::mlx_save_safetensors_writer(writer.raw(), arrays.raw(), metadata.raw()) };
304 check_status(status, operation)
305}
306
307#[derive(Debug)]
309pub struct MlxIoGguf(raw::mlx_io_gguf);
310
311impl MlxIoGguf {
312 pub fn new() -> Result<Self, MlxCError> {
314 let operation: &'static str = "create an MLX GGUF container";
315 let raw_gguf = unsafe { raw::mlx_io_gguf_new() };
317 if raw_gguf.ctx.is_null() {
318 return Err(MlxCError {
319 operation,
320 description: "MLX returned an empty GGUF handle".to_owned(),
321 });
322 }
323 Ok(Self(raw_gguf))
324 }
325
326 pub(crate) fn raw_mut(&mut self) -> *mut raw::mlx_io_gguf {
327 &mut self.0
328 }
329
330 pub fn load_from_file(path: &str, stream: &MlxStream) -> Result<Self, MlxCError> {
335 let operation: &'static str = "load an MLX GGUF container from a file";
336 let path_value = path_argument(path, operation)?;
337 let mut output = Self::new()?;
338 let status =
341 unsafe { raw::mlx_load_gguf(output.raw_mut(), path_value.as_ptr(), stream.raw()) };
342 check_status(status, operation)?;
343 Ok(output)
344 }
345
346 pub fn save_to_file(&self, path: &str) -> Result<(), MlxCError> {
351 let operation: &'static str = "save an MLX GGUF container to a file";
352 let path_value = path_argument(path, operation)?;
353 let status = unsafe { raw::mlx_save_gguf(path_value.as_ptr(), self.0) };
355 check_status(status, operation)
356 }
357
358 pub fn array_keys(&self) -> Result<MlxVectorString, MlxCError> {
360 let operation: &'static str = "list MLX GGUF array keys";
361 let mut output = MlxVectorString::empty();
362 let status = unsafe { raw::mlx_io_gguf_get_keys(output.raw_mut(), self.0) };
365 check_status(status, operation)?;
366 Ok(output)
367 }
368
369 pub fn array_at(&self, key: &str) -> Result<MlxArray, MlxCError> {
374 let operation: &'static str = "read an MLX GGUF array";
375 let key_argument = path_argument(key, operation)?;
376 let mut output = MlxArray::empty();
377 let status =
380 unsafe { raw::mlx_io_gguf_get_array(output.raw_mut(), self.0, key_argument.as_ptr()) };
381 check_status(status, operation)?;
382 output.require_populated(operation)?;
383 Ok(output)
384 }
385
386 pub fn metadata_array_at(&self, key: &str) -> Result<MlxArray, MlxCError> {
391 let operation: &'static str = "read an MLX GGUF metadata array";
392 let key_argument = path_argument(key, operation)?;
393 let mut output = MlxArray::empty();
394 let status = unsafe {
397 raw::mlx_io_gguf_get_metadata_array(output.raw_mut(), self.0, key_argument.as_ptr())
398 };
399 check_status(status, operation)?;
400 output.require_populated(operation)?;
401 Ok(output)
402 }
403
404 pub fn metadata_string_at(&self, key: &str) -> Result<String, MlxCError> {
409 let operation: &'static str = "read an MLX GGUF metadata string";
410 let key_argument = path_argument(key, operation)?;
411 let mut output = MlxString::empty();
412 let status = unsafe {
415 raw::mlx_io_gguf_get_metadata_string(output.raw_mut(), self.0, key_argument.as_ptr())
416 };
417 check_status(status, operation)?;
418 output.require_populated(operation)?;
419 Ok(output.to_string_lossy())
420 }
421
422 pub fn metadata_string_vector_at(&self, key: &str) -> Result<MlxVectorString, MlxCError> {
427 let operation: &'static str = "read an MLX GGUF metadata string vector";
428 let key_argument = path_argument(key, operation)?;
429 let mut output = MlxVectorString::empty();
430 let status = unsafe {
433 raw::mlx_io_gguf_get_metadata_vector_string(
434 output.raw_mut(),
435 self.0,
436 key_argument.as_ptr(),
437 )
438 };
439 check_status(status, operation)?;
440 Ok(output)
441 }
442
443 fn has_metadata(
445 &self,
446 key: &str,
447 probe: unsafe extern "C" fn(
448 *mut bool,
449 raw::mlx_io_gguf,
450 *const std::os::raw::c_char,
451 ) -> i32,
452 operation: &'static str,
453 ) -> Result<bool, MlxCError> {
454 let key_argument = path_argument(key, operation)?;
455 let mut exists = false;
456 let status = unsafe { probe(&mut exists, self.0, key_argument.as_ptr()) };
459 check_status(status, operation)?;
460 Ok(exists)
461 }
462
463 pub fn has_metadata_array(&self, key: &str) -> Result<bool, MlxCError> {
465 self.has_metadata(
466 key,
467 raw::mlx_io_gguf_has_metadata_array,
468 "probe MLX GGUF array metadata",
469 )
470 }
471
472 pub fn has_metadata_string(&self, key: &str) -> Result<bool, MlxCError> {
474 self.has_metadata(
475 key,
476 raw::mlx_io_gguf_has_metadata_string,
477 "probe MLX GGUF string metadata",
478 )
479 }
480
481 pub fn has_metadata_string_vector(&self, key: &str) -> Result<bool, MlxCError> {
483 self.has_metadata(
484 key,
485 raw::mlx_io_gguf_has_metadata_vector_string,
486 "probe MLX GGUF string-vector metadata",
487 )
488 }
489
490 pub fn set_array(&mut self, key: &str, value: &MlxArray) -> Result<(), MlxCError> {
492 let operation: &'static str = "store an MLX GGUF array";
493 let key_argument = path_argument(key, operation)?;
494 let status =
497 unsafe { raw::mlx_io_gguf_set_array(self.0, key_argument.as_ptr(), value.raw()) };
498 check_status(status, operation)
499 }
500
501 pub fn set_metadata_array(&mut self, key: &str, value: &MlxArray) -> Result<(), MlxCError> {
503 let operation: &'static str = "store MLX GGUF metadata array";
504 let key_argument = path_argument(key, operation)?;
505 let status = unsafe {
508 raw::mlx_io_gguf_set_metadata_array(self.0, key_argument.as_ptr(), value.raw())
509 };
510 check_status(status, operation)
511 }
512
513 pub fn set_metadata_string(&mut self, key: &str, value: &str) -> Result<(), MlxCError> {
515 let operation: &'static str = "store MLX GGUF metadata string";
516 let key_argument = path_argument(key, operation)?;
517 let value_argument = path_argument(value, operation)?;
518 let status = unsafe {
521 raw::mlx_io_gguf_set_metadata_string(
522 self.0,
523 key_argument.as_ptr(),
524 value_argument.as_ptr(),
525 )
526 };
527 check_status(status, operation)
528 }
529
530 pub fn set_metadata_string_vector(
532 &mut self,
533 key: &str,
534 values: &MlxVectorString,
535 ) -> Result<(), MlxCError> {
536 let operation: &'static str = "store MLX GGUF metadata string vector";
537 let key_argument = path_argument(key, operation)?;
538 let status = unsafe {
541 raw::mlx_io_gguf_set_metadata_vector_string(self.0, key_argument.as_ptr(), values.raw())
542 };
543 check_status(status, operation)
544 }
545}
546
547impl Drop for MlxIoGguf {
548 fn drop(&mut self) {
549 unsafe {
552 raw::mlx_io_gguf_free(self.0);
553 }
554 }
555}