kglite_c/strings.rs
1//! Owned-out-string allocation. Every C-ABI function that hands a
2//! string back to the caller goes through [`alloc_c_string`] (which
3//! returns a `*const c_char` the caller MUST free via
4//! [`kglite_free_string`]).
5//!
6//! Convention: there is exactly ONE `kglite_free_string`. We never
7//! ship per-context variants — bindings learn the single freer and
8//! reach for it for every owned string from any kglite function.
9
10use std::ffi::{c_char, CString};
11
12/// Allocate a C-owned, null-terminated UTF-8 string. The caller is
13/// responsible for freeing the returned pointer via
14/// [`kglite_free_string`]. Returns a null pointer if the input
15/// contains an embedded NUL byte (`'\0'`) — `CString` would reject
16/// it. Callers that hit this should sanitize their input.
17pub(crate) fn alloc_c_string(s: &str) -> *const c_char {
18 match CString::new(s) {
19 Ok(c) => c.into_raw().cast_const(),
20 Err(_) => std::ptr::null(),
21 }
22}
23
24/// Free a string previously returned by any `kglite_*` function.
25///
26/// Safety: `s` must be either null or a pointer previously returned
27/// by a `kglite_*` function (these all flow through
28/// [`alloc_c_string`]). Calling twice on the same pointer is UB.
29/// Calling with a pointer to a string allocated by the C caller's
30/// own `malloc` is UB.
31///
32/// Passing null is safe (treated as a no-op).
33///
34/// # Examples
35///
36/// ```c
37/// const char* col_json = kglite_cypher_result_columns_json(result);
38/// printf("%s\n", col_json);
39/// kglite_free_string(col_json);
40/// ```
41#[no_mangle]
42pub unsafe extern "C" fn kglite_free_string(s: *const c_char) {
43 if s.is_null() {
44 return;
45 }
46 // Safety: we're consuming a pointer we allocated via
47 // `CString::into_raw`. Dropping the `CString` frees the memory.
48 let _ = unsafe { CString::from_raw(s.cast_mut()) };
49}