pyroxide/lib.rs
1//! # pyroxide
2//!
3//! Zero-copy FFI bridge between Rust and Mojo — the glowing bridge
4//! between oxidation and fire.
5//!
6//! ## Safety model
7//!
8//! - **No dangling pointers**: [`MojoRef`](bridge::MojoRef) ties the pointer's
9//! validity to the Rust borrow's lifetime.
10//! - **No panics across FFI**: [`catch_mojo_call`](trampoline::catch_mojo_call)
11//! catches panics at the boundary, preventing undefined behavior.
12//! - **No layout mismatch**: [`mojo_type!`] enforces `#[repr(C)]` and zerocopy
13//! derives at compile time.
14//! - **Ownership is explicit**: Rust owns the data, Mojo borrows via pointer.
15//!
16//! ## Quick start
17//!
18//! ```rust,ignore
19//! use pyroxide::prelude::*;
20//!
21//! mojo_type! {
22//! pub struct Vec3 { pub x: f64, pub y: f64, pub z: f64 }
23//! }
24//!
25//! unsafe extern "C" {
26//! fn vec3_length(addr: isize) -> f64;
27//! }
28//!
29//! let v = Vec3 { x: 3.0, y: 4.0, z: 0.0 };
30//! let len = unsafe { vec3_length(v.as_raw()) };
31//! ```
32//!
33//! ## Feature flags
34//!
35//! - **`max`** — Types matching the Modular MAX ML framework
36//! (`DType`, `TensorShape`, `TensorDescriptor`, `Tensor`, `TensorView`)
37
38pub mod abi;
39pub mod bridge;
40pub mod string;
41pub mod trampoline;
42pub mod types;
43
44#[doc(hidden)]
45pub use zerocopy;
46
47pub mod prelude {
48 pub use crate::abi::OutParam;
49 pub use crate::bridge::{FromMojo, IntoMojo, MojoMut, MojoRef, MojoSlice, MojoSliceMut};
50 pub use crate::mojo_type;
51 pub use crate::string::MojoStr;
52 pub use crate::trampoline::catch_mojo_call;
53 #[cfg(feature = "max")]
54 pub use crate::types::max;
55 pub use crate::types::primitives::*;
56}
57
58/// Declare a struct that can safely cross the Mojo FFI boundary.
59///
60/// Adds `#[repr(C)]` and all zerocopy derives automatically.
61/// The struct implements [`IntoMojo`](bridge::IntoMojo) and
62/// [`FromMojo`](bridge::FromMojo).
63#[macro_export]
64macro_rules! mojo_type {
65 (
66 $(#[$meta:meta])*
67 $vis:vis struct $name:ident {
68 $($field_vis:vis $field:ident : $ty:ty),* $(,)?
69 }
70 ) => {
71 $(#[$meta])*
72 #[repr(C)]
73 #[derive(
74 Debug, Clone, Copy, PartialEq,
75 $crate::zerocopy::IntoBytes,
76 $crate::zerocopy::FromBytes,
77 $crate::zerocopy::Immutable,
78 $crate::zerocopy::KnownLayout,
79 )]
80 $vis struct $name {
81 $($field_vis $field : $ty),*
82 }
83 };
84}
85
86#[cfg(test)]
87mod tests {
88 use super::prelude::*;
89
90 mojo_type! {
91 pub struct TestVec2 {
92 pub x: f64,
93 pub y: f64,
94 }
95 }
96
97 #[test]
98 fn mojo_type_is_repr_c() {
99 assert_eq!(std::mem::size_of::<TestVec2>(), 16);
100 }
101
102 #[test]
103 fn mojo_type_has_into_mojo() {
104 let v = TestVec2 { x: 1.0, y: 2.0 };
105 assert_ne!(v.as_raw(), 0);
106 }
107
108 #[test]
109 fn mojo_type_has_from_mojo() {
110 let mut v = TestVec2 { x: 0.0, y: 0.0 };
111 assert_ne!(v.as_raw_mut(), 0);
112 }
113
114 #[test]
115 fn mojo_type_is_copy_and_eq() {
116 let a = TestVec2 { x: 1.0, y: 2.0 };
117 let b = a;
118 assert_eq!(a, b);
119 }
120}