Skip to main content

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}