use std::ffi::{CStr, CString}; use std::os::raw::{c_char, c_int}; #[repr(C)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct SharedStats { pub total: c_int, pub product: c_int, pub gap: c_int, } pub fn compute_stats(left: c_int, right: c_int) -> SharedStats { SharedStats { total: left.saturating_add(right), product: left.saturating_mul(right), gap: left.saturating_sub(right).abs(), } } pub fn make_rust_message(name: &str, left: c_int, right: c_int) -> String { let stats = compute_stats(left, right); format!( "Rust handled {name}: total={}, product={}, gap={}", stats.total, stats.product, stats.gap ) } #[no_mangle] pub unsafe extern "C" fn rust_compute_stats( left: c_int, right: c_int, out_stats: *mut SharedStats, ) -> c_int { if out_stats.is_null() { return 1; } out_stats.write(compute_stats(left, right)); 0 } #[no_mangle] pub unsafe extern "C" fn rust_make_message( name: *const c_char, left: c_int, right: c_int, ) -> *mut c_char { if name.is_null() { return string_into_raw("Rust received a null name pointer".to_string()); } let name = CStr::from_ptr(name).to_string_lossy(); string_into_raw(make_rust_message(name.as_ref(), left, right)) } #[no_mangle] pub unsafe extern "C" fn rust_free_string(ptr: *mut c_char) { if ptr.is_null() { return; } drop(CString::from_raw(ptr)); } fn string_into_raw(message: String) -> *mut c_char { let sanitized = message.replace('\0', "?"); match CString::new(sanitized) { Ok(c_string) => c_string.into_raw(), Err(_) => std::ptr::null_mut(), } } #[cfg(test)] mod tests { use super::*; #[test] fn compute_stats_matches_expected_values() { assert_eq!( compute_stats(9, 4), SharedStats { total: 13, product: 36, gap: 5, } ); } #[test] fn message_contains_name_and_values() { let message = make_rust_message("Ada", 7, 5); assert!(message.contains("Ada")); assert!(message.contains("total=12")); assert!(message.contains("product=35")); assert!(message.contains("gap=2")); } }