1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
//! **A simple isolated dynamic FFI framework for Rust.**
//!
//! `chillffi` allows dynamically loading C libraries `.so` and calling their
//! functions at runtime, **isolating the calls in a separate empty process**.
//!
//! If third-party C code crashes or corrupts something, your main Rust
//! application will continue running.
//!
//! _(In the future, an expansion of the functionality for working with FFI is
//! planned.)_
//!
//! # Platform support
//!
//! Supported only on Unix-like OSes, and tested only on Linux.
//!
//! _(Planned: Windows, macOS, WASM, Bare metal.)_
//!
//! # Features
//!
//! - **Crash Isolation**: A crash or panic inside unreliable FFI code does not
//! break or corrupt the main process.
//! - **Zygote Model**: Fast forking and spawning of isolated workers with
//! minimal overhead.
//! - **In-memory IPC**: Transfer of file descriptors and data through sockets
//! without accessing the disk.
//! - **Dynamic FFI**: On-the-fly function calls without the need to compile
//! static C bindings.
//!
//! # Quick start
//!
//! ```no_run
//! use chillffi::ffi::value::{Type, Value};
//! use chillffi::ffi;
//!
//! fn main() -> ()
//! {
//! // Perform an FFI call inside an isolated context using a macro
//! let result: Value = ffi!{
//! // 1. Dynamically load the system library
//! let libm: Library = Library::load("libm.so.6")?;
//!
//! // 2. Prepare the argument vector
//! let args: Vec<Value> = vec![Value::F64(4.0)];
//!
//! // 3. Call the "sqrt" function, specifying the expected return type Type::F64
//! Ok(libm.call("sqrt", args, Type::F64)?)
//!
//! // Here libm will be automatically cleared due to drop() when exiting the closure.
//! // You can also do this manually via drop(libm) or libm.unload()?
//! }.expect("FFI call failed");
//!
//! // Process the typed result
//! match result
//! {
//! Value::F64(val) =>
//! {
//! println!("sqrt(4.0) = {}", val);
//! assert!((val - 2.0).abs() < f64::EPSILON, "sqrt(4.0) != 2.0");
//! }
//! _ => panic!("Unexpected return type for sqrt"),
//! }
//! }
//! ```
//!
//! For memory-sensitive operations — C strings, out-parameters, or raw
//! buffers — use the scoped variant with [`Scope`](crate::ffi::scope::Scope)
//! and [`AllocatedMemory`](crate::ffi::allocatedMemory::AllocatedMemory):
//!
//! ```no_run
//! use chillffi::ffi::allocatedMemory::{AllocatedMemory};
//! use chillffi::ffi::value::{Type, Value};
//! use chillffi::ffi::errors::FFIError;
//! use chillffi::ffi;
//!
//! fn main() -> ()
//! {
//! // clock_gettime(CLOCK_REALTIME, ×pec) — struct out-param via Alloc/ReadMemory,
//! // the case a plain Value::Pointer can't cover on its own.
//! let (secs, nanos): (i64, i64) = ffi!(|scope| {
//! let libc: Library = Library::load("libc.so.6")?;
//!
//! // struct timespec { time_t tv_sec; long tv_nsec; } — 16 bytes on x86_64 Linux
//! let mem: AllocatedMemory = scope.alloc(16)?;
//!
//! libc.call("clock_gettime", vec![Value::I32(0 /* CLOCK_REALTIME */), mem.asPointer()], Type::I32)?;
//!
//! let Value::RawString(bytes) = mem.read()? else {
//! panic!("expected bytes")
//! };
//! drop(mem);
//!
//! let secs: i64 = i64::from_ne_bytes(bytes[0..8].try_into().unwrap());
//! let nanos: i64 = i64::from_ne_bytes(bytes[8..16].try_into().unwrap());
//! Ok((secs, nanos))
//! }).expect("clock_gettime failed");
//!
//! println!("clock_gettime(CLOCK_REALTIME) = {}.{:09}", secs, nanos);
//! }
//! ```
//!
//! For more detailed examples, see the `examples` folder.
//!
//! You can also run them via `cargo run --example <name>`.
//!
//! # Why is this convenient
//!
//! In general practice, we are used to doing it like in Python and other
//! programming languages — precisely specifying all the wrappers for FFI.
//! After which we observe how FFI still crashes anyway and the libraries are
//! not built, and the code does not work.
//!
//! This is all because FFI requires a manual bridge and it is not always
//! possible to make one.
//!
//! **chillffi** works on a different principle — you can write any FFI code
//! inside isolated blocks. Because FFI should not be scattered throughout your
//! code — this is an unsafe approach. Therefore, we write it in isolation and
//! preferably briefly, only when necessary.
//!
//! Since everything is located in isolated processes — we do not damage the
//! main runtime in any way and do not touch your code. All FFI requests work
//! in a sterile manner and in case of errors will clearly let you know about
//! it. You can also simply ignore them if you want.
//!
//! As a result, we can freely and simply write:
//! - Test code
//! - Educational code
//! - FFI bridges
//! - Dynamic programming languages
//! - Game engines
//! - Reactive systems and dynamic systems
//! - And many other things
//!
//! This is also different from the WASM approach — because we preserve a true
//! native execution here.
//!
//! # How it works
//!
//! 1. Before your code starts running, a Zygote is created — it is an empty
//! process for cloning itself and isolating FFI.
//! 2. When work with FFI is required — a copy is created from the zygote.
//! 3. Data and descriptors are transferred through a secure socket channel in
//! memory.
//! 4. In case of errors, the supervisor intercepts the worker crash and returns
//! the error to Rust, keeping your application stable.
//!
//! # Modules
//!
//! - [`mod@ffi`] — public API: [`Library`](crate::ffi::library::Library),
//! [`Value`](crate::ffi::value::Value), [`Type`](crate::ffi::value::Type),
//! [`Scope`](crate::ffi::scope::Scope),
//! [`AllocatedMemory`](crate::ffi::allocatedMemory::AllocatedMemory),
//! and error types.
//!
//! # License
//!
//! The source code is distributed under the FCL license.
//! See the repository for the full text.
// =================================================================================================
compile_error!;
// Currently available only on Linux, although it should work on UNIX in general.
// But I have not tested it on macOS.
// =================================================================================================
// =================================================================================================
use ;
use crate;
// =================================================================================================
/// Single entry point for zygote initialization in any binary (including tests).
/// Checks whether the process is running as a zygote; if so — switches to daemon mode,
/// otherwise — initializes the parent side.
// =================================================================================================
/// Internal items re-exported for the `ffi!` macro.
/// Not part of the public API; do not use directly.
/// Main macro for working with FFI.
///
/// It creates a copy of the zygote from the main zygote.
///
/// After that, any FFI code can be executed inside it.
///
/// Library specifically blocks FFI calls outside this macro.
///
/// Isolation allows adding FFI insertions without breaking or corrupting the main runtime.
///
/// todo
/// Important: It will take ownership of the Library type — therefore, the code inside
/// will have to specify it differently when this data type matches. However, this will
/// be quite rare, because FFI insertions should be rare and it is not guaranteed that
/// exactly Library will end up there.
/// The simplest solution would be for the user to rename the type — then they will not
/// see errors for their Library type.
// =================================================================================================