integrations/rust/interop.rs

99 lines
2.3 KiB
Rust

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"));
}
}