]> piware.de Git - learn-rust.git/commitdiff
Call Rust function from C: Complex cases
authorMartin Pitt <martin@piware.de>
Fri, 27 Aug 2021 12:49:22 +0000 (14:49 +0200)
committerMartin Pitt <martin@piware.de>
Fri, 27 Aug 2021 12:51:54 +0000 (14:51 +0200)
Add three functions which take/return strings and string arrays. This
creates some interesting problems how to combine the C semantics of
returning a char** with the Rust semantics of freeing objects once they
go out of scope.

This now also relies on the Rust standard library, which needs a few
system libraries. Link them.

call-rust-from-c/Makefile
call-rust-from-c/src/lib.rs
call-rust-from-c/src/main.c

index 66a951a8efd5c754d7c266f2566ab478658360b6..672874b5edcc59d3bac8f96b790b06dfd541c370 100644 (file)
@@ -1,7 +1,8 @@
 RLIB = target/debug/libcall_rust_from_c.a
+LIBS = -lpthread -ldl
 
 main: src/main.o $(RLIB)
-       $(CC) -Wall -o $@ $^
+       $(CC) -Wall -o $@ $^ $(LIBS)
 
 src/main.o: src/rustlib.h
 
index f5e43842b908f49ffbbe5e3799f75156aa186521..6a87bea780743861980f7d615fe53ac0ca0a5f45 100644 (file)
@@ -1,3 +1,4 @@
+use std::ffi::{CStr, CString};
 use std::os::raw;
 
 /// Return The Answer.
@@ -14,6 +15,54 @@ pub extern "C" fn answer() -> raw::c_int {
     return 42;
 }
 
+#[no_mangle]
+pub extern "C" fn r_strlen(s: *const raw::c_char) -> usize {
+    unsafe { CStr::from_ptr(s) }.to_bytes().len()
+}
+
+/// Return a vector of pointers as C pointer array
+///
+/// The memory of `vec` gets leaked, as otherwise Rust would free it once the vector
+/// goes out of scope, and C would access invalid memory.
+fn return_c_vec<T>(mut vec: Vec<T>) -> *mut T {
+    let p = vec.as_mut_ptr();
+    // unref vector so that it does not get freed when going out of scope
+    std::mem::forget(vec);
+    p
+}
+
+#[no_mangle]
+pub extern "C" fn r_strlist() -> *mut *const raw::c_uchar {
+    let v = vec![
+        "Hello\0".as_ptr(),
+        "World\0".as_ptr()
+        ];
+    return_c_vec(v)
+}
+
+fn impl_grep<'a>(needle: &str, haystack: &'a str) -> Vec<&'a str> {
+    haystack.lines()
+        .filter(|line| line.contains(needle))
+        .collect()
+}
+
+#[no_mangle]
+pub extern "C" fn r_grep(needle: *const raw::c_char, haystack: *const raw::c_char) -> *mut *const raw::c_char {
+    let haystack_cstr = unsafe { CStr::from_ptr(haystack).to_str() }.unwrap();
+    let strvec = impl_grep(unsafe { CStr::from_ptr(needle).to_str().unwrap() }, haystack_cstr);
+
+    // Vec[str] -> Vec[const char*]
+    let p_vec: Vec<_> = strvec.into_iter()
+        .map(|s| {
+            let s = CString::new(s).unwrap();
+            let p = s.as_ptr();
+            std::mem::forget(s);
+            p
+        })
+        .collect();
+
+    return_c_vec(p_vec)
+}
 
 #[cfg(test)]
 mod tests {
@@ -23,4 +72,9 @@ mod tests {
     fn test_answer() {
         assert_eq!(answer(), 42);
     }
+
+    #[test]
+    fn test_grep() {
+        assert_eq!(impl_grep("ell", "Hello\nworld\ncan you tell?"), vec!["Hello", "can you tell?"]);
+    }
 }
index b514433ce6fc8d680ffa67b9090ee60ed6afdb9a..a8f7dbcc4816e951bc91ce10b6e4ccd19e0290e2 100644 (file)
@@ -3,6 +3,20 @@
 
 int main()
 {
+    const char *hello = "hello";
+    const char *haystack = "Hello\nworld\ncan you tell?";
+
     printf("The answer: %i\n", answer());
+    printf("Length of '%s': %u\n", hello, r_strlen(hello));
+
+    const unsigned char **strs = r_strlist();
+    for (const unsigned char **s = strs; *s; s++)
+        printf("returned string array element: %s\n", *s);
+    free (strs);
+
+    const char **matches = r_grep("ell", "Hello\nworld\ncan you tell?");
+    for (const char **m = matches; *m; m++)
+        printf("matched line: %s\n", *m);
+    free (matches);
     return 0;
 }