kv/
kv.rs

1#![warn(unsafe_op_in_unsafe_fn)]
2#![allow(non_camel_case_types)]
3#![allow(unused_unsafe)]
4
5use ffizz_passby::Boxed;
6use ffizz_string::{fz_string_t as kvstore_string_t, FzString};
7use std::collections::HashMap;
8
9ffizz_header::snippet! {
10#[ffizz(name="header", order=0)]
11/// This library implements a simple in-memory key-value store.
12}
13
14pub struct Store {
15    map: HashMap<String, String>,
16}
17
18impl Store {
19    fn new() -> Store {
20        Store {
21            map: HashMap::new(),
22        }
23    }
24
25    fn get(&self, key: &str) -> Option<&str> {
26        self.map.get(key).map(|v| &**v)
27    }
28
29    fn set(&mut self, key: impl Into<String>, value: impl Into<String>) {
30        self.map.insert(key.into(), value.into());
31    }
32
33    fn del(&mut self, key: &str) {
34        self.map.remove(key);
35    }
36}
37
38#[ffizz_header::item]
39#[ffizz(order = 10)]
40/// This opaque pointer type represents a key-value store.
41///
42/// # Safety
43///
44/// In a multi-threaded program, kvstore_t values may be passed from thread to thread, but
45/// _must not_ be accessed concurrently from multiple threads.
46///
47/// Each kvstore_t created with `kvstore_new` must later be freed with `kvstore_free`, and
48/// once freed must not be used again.
49///
50/// Keys and values must be valid UTF-8 strings.
51///
52/// ```c
53/// typedef struct kvstore_t kvstore_t;
54/// ```
55#[allow(non_camel_case_types)]
56type kvstore_t = Store;
57
58type BoxedStore = Boxed<Store>;
59
60#[ffizz_header::item]
61#[ffizz(order = 20)]
62/// Create a new kvstore_t.
63///
64/// # Safety
65///
66/// The returned kvstore_t must be freed with kvstore_free.
67///
68/// ```c
69/// kvstore_t *kvstore_new();
70/// ```
71#[no_mangle]
72pub unsafe extern "C" fn kvstore_new() -> *mut kvstore_t {
73    let store = Store::new();
74    // SAFETY: function docs indicate value must be freed
75    unsafe { BoxedStore::return_val(store) }
76}
77
78#[ffizz_header::item]
79#[ffizz(order = 21)]
80/// Free a kvstore_t.
81///
82/// # Safety
83///
84/// The argument must be non-NULL and point to a valid kvstore_t. After this call it is no longer
85/// valid and must not be used.
86///
87/// ```c
88/// void kvstore_free(*kvstore_t);
89/// ```
90#[no_mangle]
91pub unsafe extern "C" fn kvstore_free(store: *mut kvstore_t) {
92    // SAFETY:
93    //  - store is valid and not NULL (see docstring)
94    //  - caller will not use store after this call (see docstring)
95    let store = unsafe { BoxedStore::take_nonnull(store) };
96    drop(store); // (Rust would do this anyway, but let's be explicit)
97}
98
99#[ffizz_header::item]
100#[ffizz(order = 30)]
101/// Get a value from the kvstore_t.  If the value is not found, the returned string is a Null
102/// variant (test with `kvstore_string_is_null`).
103///
104/// # Safety
105///
106/// The store must be non-NULL and point to a valid kvstore_t.
107///
108/// The key argument must be a valid kvstore_string_t.  The caller must free both the key and the
109/// returned string, if any.
110/// ```c
111/// fz_string_t kvstore_get(kvstore_t *store, kvstore_string_t *key);
112/// ```
113#[no_mangle]
114pub unsafe extern "C" fn kvstore_get(
115    store: *mut kvstore_t,
116    key: *mut kvstore_string_t,
117) -> kvstore_string_t {
118    // SAFETY:
119    // - store is not NULL and valid (see docstring)
120    // - store is valid for the life of this function (documented as not threadsafe)
121    // - store will not be accessed during the life of this function (documented as not threadsafe)
122    unsafe {
123        BoxedStore::with_ref_nonnull(store, |store| {
124            // SAFETY:
125            //  - key must be a valid kvstore_string_t (docstring)
126            //  - key will not be accessed concurrency (type docstring)
127            match unsafe {
128                FzString::with_ref_mut(key, |key| {
129                    if let Ok(Some(key)) = key.as_str() {
130                        store.get(key)
131                    } else {
132                        None // Null key or invalid UTF-8 looks the same as key-not-found
133                    }
134                })
135            } {
136                // SAFETY:
137                //  - the caller will free the returned value (see docstring)
138                Some(val) => unsafe { FzString::return_val(FzString::String(val.to_string())) },
139                // SAFETY:
140                //  - the caller will free the returned value (see docstring)
141                None => unsafe { FzString::return_val(FzString::Null) },
142            }
143        })
144    }
145}
146
147#[ffizz_header::item]
148#[ffizz(order = 30)]
149/// Set a value in the kvstore_t, consuming the key and value.  Returns false on error.
150///
151/// # Safety
152///
153/// The store must be non-NULL and point to a valid kvstore_t.
154///
155/// The key and value must both be valid kvstore_string_t values, must not be otherwise accessed
156/// while this function executes, and are invalid when this function returns.
157///
158/// # Note
159///
160/// The kvstore API sometimes invalidates its string arguments and sometimes leaves that
161/// reponsibility to the caller, which could lead to confusion for users of the library. It's done
162/// here for example purposes only!
163///
164/// ```c
165/// bool kvstore_set(kvstore *store, kvstore_string_t *key, kvstore_string_t *value);
166/// ```
167#[no_mangle]
168pub unsafe extern "C" fn kvstore_set(
169    store: *mut kvstore_t,
170    key: *mut kvstore_string_t,
171    val: *mut kvstore_string_t,
172) -> bool {
173    // SAFETY:
174    // - store is not NULL and valid (see docstring)
175    // - store is valid for the life of this function (documented as not threadsafe)
176    // - store will not be accessed during the life of this function (documented as not threadsafe)
177    unsafe {
178        BoxedStore::with_ref_mut_nonnull(store, |store| {
179            // SAFETY:
180            //  - key/val are valid kvstore_string_t's (see docstring)
181            //  - key/val are not accessed concurrently (type docstring)
182            //  - key/val are not uesd after function returns (see docstring)
183            let (key, val) = unsafe { (FzString::take_ptr(key), FzString::take_ptr(val)) };
184
185            if let Ok(Some(key)) = key.into_string() {
186                if let Ok(Some(val)) = val.into_string() {
187                    store.set(key, val);
188                    return true;
189                }
190            }
191            false
192        })
193    }
194}
195
196#[ffizz_header::item]
197#[ffizz(order = 30)]
198/// Delete a value from the kvstore_t.  Returns false on error.
199///
200/// # Safety
201///
202/// The store must be non-NULL and point to a valid kvstore_t.
203///
204/// The key must be a valid kvstore_string_t, must not be otherwise accessed while this function
205/// executes, and will remain valid after this function returns.
206/// ```c
207/// bool kvstore_del(kvstore *store, kvstore_string_t *key);
208/// ```
209#[no_mangle]
210pub unsafe extern "C" fn kvstore_del(store: *mut kvstore_t, key: *mut kvstore_string_t) -> bool {
211    // SAFETY:
212    //  - key must be a valid kvstore_string_t (docstring)
213    //  - key will not be accessed concurrency (type docstring)
214    unsafe {
215        FzString::with_ref_mut(key, move |key| {
216            // SAFETY:
217            // - store is not NULL and valid (see docstring)
218            // - store is valid for the life of this function (documented as not threadsafe)
219            // - store will not be accessed during the life of this function (documented as not threadsafe)
220            unsafe {
221                BoxedStore::with_ref_mut_nonnull(store, move |store| {
222                    if let Ok(Some(key)) = key.as_str() {
223                        store.del(key);
224                        true
225                    } else {
226                        false
227                    }
228                })
229            }
230        })
231    }
232}
233
234ffizz_header::snippet! {
235#[ffizz(name="kvstore_string_t", order=100)]
236/// kvstore_string_t represents a string suitable for use with kvstore, as an opaque
237/// stack-allocated value.
238///
239/// This value can contain either a string or a special "Null" variant indicating there is no
240/// string.  When functions take a `kvstore_string_t*` as an argument, the NULL pointer is treated as
241/// the Null variant.  Note that the Null variant is not necessarily represented as the zero value
242/// of the struct.
243///
244/// # Safety
245///
246/// A kvstore_string_t must always be initialized before it is passed as an argument.  Functions
247/// returning a `kvstore_string_t` return an initialized value.
248///
249/// Each initialized kvstore_string_t must be freed, either by calling kvstore_string_free or by
250/// passing the string to a function which takes ownership of the string.
251///
252/// For a given kvstore_string_t value, API functions must not be called concurrently.  This includes
253/// "read only" functions such as kvstore_string_content.
254///
255/// ```c
256/// typedef struct kvstore_string_t {
257///     size_t __reserved[4];
258/// };
259/// ```
260}
261
262// re-export some of the kvstore_string_* as kvstore_string_*
263
264#[ffizz_header::item]
265#[ffizz(order = 110)]
266/// Create a new `kvstore_string_t` by cloning the content of the given C string.  The resulting `fz_string_t`
267/// is independent of the given string.
268///
269/// # Safety
270///
271/// The given pointer must not be NULL.
272///
273/// ```c
274/// kvstore_string_t kvstore_string_clone(const char *);
275/// ```
276pub use ffizz_string::fz_string_clone as kvstore_string_clone;
277
278#[ffizz_header::item]
279#[ffizz(order = 110)]
280/// Get the content of the string as a regular C string.
281///
282/// A string contianing NUL bytes will result in a NULL return value.  In general, prefer
283/// `kvstore_string_content_with_len` except when it's certain that the string is NUL-free.
284///
285/// The Null variant also results in a NULL return value.
286///
287/// This function takes the kvstore_string_t by pointer because it may be modified in-place to add a NUL
288/// terminator.  The pointer must not be NULL.
289///
290/// # Safety
291///
292/// The returned string is "borrowed" and remains valid only until the kvstore_string_t is freed or
293/// passed to any other API function.
294pub use ffizz_string::fz_string_content as kvstore_string_content;
295
296#[ffizz_header::item]
297#[ffizz(order = 110)]
298/// Free a kvstore_string_t.
299///
300/// # Safety
301///
302/// The string must not be used after this function returns, and must not be freed more than once.
303/// It is safe to free Null-variant strings.
304///
305/// ```c
306/// kvstore_string_free(kvstore_string_t *);
307/// ```
308pub use ffizz_string::fz_string_free as kvstore_string_free;
309
310#[ffizz_header::item]
311#[ffizz(order = 110)]
312/// Determine whether the given kvstore_string_t is a Null variant.
313///
314/// ```c
315/// bool kvstore_string_is_null(kvstore_string_t *);
316/// ```
317pub use ffizz_string::fz_string_is_null as kvstore_string_is_null;
318
319// Calling a C API from Rust is tricky, and not what ffizz is about.  This section serves as a
320// test of the example code above, with equivalent C code included in comments.
321fn main() {
322    // kvstore_t *store = kvstore_new();
323    let store = unsafe { kvstore_new() };
324
325    /// Clone a Rust string into an kvstore_string_t
326    fn fzstr(s: &str) -> kvstore_string_t {
327        use std::ffi::CString;
328        let cstr = CString::new(s).unwrap();
329        unsafe { kvstore_string_clone(cstr.as_ptr()) }
330    }
331
332    /// Get a Rust &str containing the data in an kvstore_string_t
333    fn rstr(fzs: &mut kvstore_string_t) -> &str {
334        use std::ffi::CStr;
335        let content =
336            unsafe { CStr::from_ptr(kvstore_string_content(fzs as *mut kvstore_string_t)) };
337        content.to_str().unwrap()
338    }
339
340    // kvstore_string_t key = kvstore_string_clone("a-key");
341    let mut key = fzstr("a-key");
342
343    // kvstore_string_t val = kvstore_get(store, key)
344    let mut val = unsafe { kvstore_get(store, &mut key as *mut kvstore_string_t) };
345
346    // assert(kvstore_string_is_null(val));
347    assert!(unsafe { kvstore_string_is_null(&val as *const kvstore_string_t) });
348
349    // kvstore_string_free(val);
350    unsafe { kvstore_string_free(&mut val as *mut kvstore_string_t) };
351
352    // val = kvstore_string_clone("a-val");
353    let mut val = fzstr("a-val");
354
355    // assert(kvstore_set(store, key, val));
356    assert!(unsafe {
357        kvstore_set(
358            store,
359            &mut key as *mut kvstore_string_t,
360            &mut val as *mut kvstore_string_t,
361        )
362    });
363
364    // key = kvstore_string_clone("a-key");
365    let mut key = fzstr("a-key");
366
367    // val = kvstore_get(store, key)
368    let mut val = unsafe { kvstore_get(store, &mut key as *mut kvstore_string_t) };
369
370    // assert(0 == strcmp(kvstore_string_content(val), "a-val"));
371    assert_eq!(rstr(&mut val), "a-val");
372
373    // kvstore_string_free(val);
374    unsafe { kvstore_string_free(&mut val as *mut kvstore_string_t) };
375
376    // assert(kvstore_del(store, key));
377    assert!(unsafe { kvstore_del(store, &mut key as *mut kvstore_string_t,) });
378
379    // val = kvstore_get(store, key)
380    let mut val = unsafe { kvstore_get(store, &mut key as *mut kvstore_string_t) };
381
382    // assert(kvstore_string_is_null(val));
383    assert!(unsafe { kvstore_string_is_null(&val as *const kvstore_string_t) });
384
385    // kvstore_string_free(key);
386    unsafe { kvstore_string_free(&mut key as *mut kvstore_string_t) };
387
388    // kvstore_string_free(val);
389    unsafe { kvstore_string_free(&mut val as *mut kvstore_string_t) };
390
391    // kvstore_free(store);
392    unsafe { kvstore_free(store) };
393}