Skip to main content

laminar_db/ffi/
memory.rs

1//! FFI memory management functions.
2//!
3//! Provides `extern "C"` functions for freeing memory allocated by `LaminarDB`.
4
5use std::ffi::{c_char, CString};
6
7/// Transfer ownership of a `CString` to a raw pointer.
8///
9/// The caller is responsible for freeing this with `laminar_string_free`.
10pub(crate) fn take_ownership_string(s: CString) -> *mut c_char {
11    s.into_raw()
12}
13
14/// Free a string allocated by `LaminarDB`.
15///
16/// # Arguments
17///
18/// * `s` - String pointer to free, or NULL
19///
20/// # Safety
21///
22/// `s` must be a pointer returned by a laminar function, or NULL.
23/// After calling this function, the pointer is invalid.
24#[no_mangle]
25pub unsafe extern "C" fn laminar_string_free(s: *mut c_char) {
26    if !s.is_null() {
27        // SAFETY: s is non-null and was allocated by CString::into_raw
28        drop(unsafe { CString::from_raw(s) });
29    }
30}
31
32/// Get the `LaminarDB` library version.
33///
34/// # Returns
35///
36/// A pointer to a null-terminated version string. This is a static string
37/// and should NOT be freed.
38///
39/// # Safety
40///
41/// The returned pointer is valid for the lifetime of the process.
42#[no_mangle]
43pub extern "C" fn laminar_version() -> *const c_char {
44    // Static string, never freed
45    static VERSION: &[u8] = concat!(env!("CARGO_PKG_VERSION"), "\0").as_bytes();
46    VERSION.as_ptr().cast()
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52    use std::ffi::CStr;
53
54    #[test]
55    fn test_string_free_null() {
56        // Should not crash
57        // SAFETY: Testing null handling
58        unsafe {
59            laminar_string_free(std::ptr::null_mut());
60        }
61    }
62
63    #[test]
64    fn test_string_round_trip() {
65        let original = "Hello, FFI!";
66        let c_string = CString::new(original).unwrap();
67        let ptr = take_ownership_string(c_string);
68
69        // SAFETY: ptr was just created
70        let retrieved = unsafe { CStr::from_ptr(ptr).to_str().unwrap() };
71        assert_eq!(retrieved, original);
72
73        // SAFETY: ptr is valid
74        unsafe { laminar_string_free(ptr) };
75    }
76
77    #[test]
78    fn test_version() {
79        let ptr = laminar_version();
80        assert!(!ptr.is_null());
81
82        // SAFETY: ptr points to a static string
83        let version = unsafe { CStr::from_ptr(ptr).to_str().unwrap() };
84        assert!(!version.is_empty());
85        // Should contain version number pattern
86        assert!(version.chars().any(|c| c.is_ascii_digit()));
87    }
88}