ragc_core/ffi/
segment_helpers.rs

1// FFI helpers for segmentation - micro-functions callable from C++
2// Ship of Theseus approach: replace tiny pieces one at a time
3
4use std::os::raw::c_char;
5use std::slice;
6
7/// C representation of a contig slice result
8#[repr(C)]
9pub struct ContigSlice {
10    /// Pointer to the slice data
11    pub data: *mut u8,
12    /// Length of the slice
13    pub len: usize,
14}
15
16/// Extract a slice of a contig
17///
18/// Matches C++ AGC's get_part() function (agc_compressor.cpp:2101-2107):
19/// ```cpp
20/// contig_t CAGCCompressor::get_part(const contig_t& contig, uint64_t pos, uint64_t len)
21/// {
22///     if (pos + len < contig.size())
23///         return contig_t(contig.begin() + pos, contig.begin() + pos + len);
24///     else
25///         return contig_t(contig.begin() + pos, contig.end());
26/// }
27/// ```
28///
29/// # Safety
30/// - Caller must ensure contig_data points to valid memory of contig_len bytes
31/// - Returned slice is owned by caller and must be freed with ragc_free_contig_slice()
32#[no_mangle]
33pub extern "C" fn ragc_get_contig_part(
34    contig_data: *const u8,
35    contig_len: usize,
36    pos: u64,
37    len: u64,
38) -> ContigSlice {
39    unsafe {
40        let contig = slice::from_raw_parts(contig_data, contig_len);
41
42        let pos = pos as usize;
43        let len = len as usize;
44
45        // Match C++ logic exactly
46        let result_slice = if pos + len < contig.len() {
47            // Can extract full requested length
48            &contig[pos..pos + len]
49        } else {
50            // Would exceed contig end, return until end
51            &contig[pos..]
52        };
53
54        // Allocate owned copy for C++
55        let mut result = result_slice.to_vec();
56        let result_ptr = result.as_mut_ptr();
57        let result_len = result.len();
58
59        // Prevent Rust from freeing the allocation
60        std::mem::forget(result);
61
62        ContigSlice {
63            data: result_ptr,
64            len: result_len,
65        }
66    }
67}
68
69/// Free a contig slice allocated by ragc_get_contig_part()
70///
71/// # Safety
72/// - Must only be called once per ContigSlice returned from ragc_get_contig_part()
73/// - slice.data must be a valid pointer from ragc_get_contig_part()
74#[no_mangle]
75pub extern "C" fn ragc_free_contig_slice(slice: ContigSlice) {
76    unsafe {
77        if !slice.data.is_null() && slice.len > 0 {
78            // Reconstruct the Vec and let it drop
79            let _ = Vec::from_raw_parts(slice.data, slice.len, slice.len);
80        }
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn test_get_part_full_length() {
90        let contig = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
91        let slice = ragc_get_contig_part(contig.as_ptr(), contig.len(), 2, 5);
92
93        unsafe {
94            let result = slice::from_raw_parts(slice.data, slice.len);
95            assert_eq!(result, &[2, 3, 4, 5, 6]);
96        }
97
98        ragc_free_contig_slice(slice);
99    }
100
101    #[test]
102    fn test_get_part_exceeds_length() {
103        let contig = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
104        let slice = ragc_get_contig_part(contig.as_ptr(), contig.len(), 7, 10);
105
106        unsafe {
107            let result = slice::from_raw_parts(slice.data, slice.len);
108            assert_eq!(result, &[7, 8, 9]);
109        }
110
111        ragc_free_contig_slice(slice);
112    }
113
114    #[test]
115    fn test_get_part_exact_end() {
116        let contig = vec![0, 1, 2, 3, 4];
117        let slice = ragc_get_contig_part(contig.as_ptr(), contig.len(), 2, 3);
118
119        unsafe {
120            let result = slice::from_raw_parts(slice.data, slice.len);
121            assert_eq!(result, &[2, 3, 4]);
122        }
123
124        ragc_free_contig_slice(slice);
125    }
126}