ffi_pool/
lib.rs

1//! # `ffi-pool`: useful object pool types for FFI code
2//!
3//! This crate contains some useful object pool types for interfacing with C code (at the moment,
4//! just `CStringPool`.)
5
6
7#[cfg(test)]
8#[macro_use]
9extern crate lazy_static;
10
11extern crate memchr;
12extern crate objpool;
13extern crate take_mut;
14
15use std::error::Error;
16use std::ffi::{CStr, CString};
17use std::fmt;
18use std::sync::Arc;
19
20use objpool::{Item, Pool};
21
22
23/// An error returned upon finding a nul byte in a string we are attempting to convert to a
24/// `CString`.
25#[derive(Debug, Clone, Copy)]
26pub struct NulError {
27    pub position: usize,
28}
29
30
31impl fmt::Display for NulError {
32    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
33        write!(f, "nul byte found in provided data at position: {}", self.position)
34    }
35}
36
37
38impl Error for NulError {
39    fn description(&self) -> &str { "nul byte found in data" }
40}
41
42
43/// A thread-safe pool of `CString`s which can be readily reused with `str`s for ease of FFI interactions.
44#[derive(Debug, Clone)]
45pub struct CStringPool {
46    pool: Arc<Pool<CString>>,
47}
48
49
50impl CStringPool {
51    /// Create a new pool with a given default capacity for newly allocated `CString`s.
52    pub fn new(default_string_capacity: usize) -> CStringPool {
53        CStringPool {
54            pool: Pool::new(move || {
55                let vec = Vec::with_capacity(default_string_capacity);
56
57                // The `Vec` is empty and thus contains no nul bytes.
58                unsafe { CString::from_vec_unchecked(vec) }
59            }),
60        }
61    }
62
63
64    /// Create a new pool with an additional maximum capacity. Allocating new `CString`s when the
65    /// pool is at capacity will block until a new `CString` is available.
66    pub fn with_capacity(pool_capacity: usize, default_string_capacity: usize) -> CStringPool {
67        CStringPool {
68            pool: Pool::with_capacity(pool_capacity, move || {
69                let vec = Vec::with_capacity(default_string_capacity);
70
71                // The `Vec` is empty and thus contains no nul bytes.
72                unsafe { CString::from_vec_unchecked(vec) }
73            }),
74        }
75    }
76
77
78    /// Allocate a new `CString` from the pool. This will check the supplied `str` for interior nul
79    /// bytes.
80    pub fn get_str<T: AsRef<str>>(&self, s: T) -> Result<Item<CString>, NulError> {
81        let str_ref = s.as_ref();
82
83        // Ensure our str contains no nul bytes and is thus safe to inject into a `CString`.
84        if let Some(i) = memchr::memchr(0, str_ref.as_bytes()) {
85            return Err(NulError { position: i });
86        }
87
88        let mut item = self.pool.get();
89        take_mut::take(&mut *item, |cstring| {
90            // We are guaranteed that if a `CString` is in the pool, it is either empty or created from
91            // an `&str`. Thus, it is safe to convert as it *always* contains valid unicode data.
92            let mut string = unsafe { String::from_utf8_unchecked(cstring.into_bytes()) };
93
94            string.clear();
95            string.push_str(str_ref);
96
97            // We check for nul bytes outside of this block so that we can return an error instead of
98            // panicking.
99            unsafe { CString::from_vec_unchecked(string.into_bytes()) }
100        });
101
102        Ok(item)
103    }
104
105
106    /// Allocate a new `CString` from the pool, using a `CStr` as a source.
107    pub fn get_c_str<T: AsRef<CStr>>(&self, s: T) -> Item<CString> {
108        let str_ref = s.as_ref();
109
110        let mut item = self.pool.get();
111        take_mut::take(&mut *item, |cstring| {
112            let mut bytes = cstring.into_bytes();
113
114            bytes.clear();
115            bytes.extend(str_ref.to_bytes());
116
117            // These bytes came from a `CStr`. There is no way they have a nul byte inside.
118            unsafe { CString::from_vec_unchecked(bytes) }
119        });
120
121        item
122    }
123}
124
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    lazy_static! {
131        static ref POOL: CStringPool = CStringPool::new(128);
132    }
133
134    #[test]
135    fn round_trip() {
136        let s = "foo";
137        let cstr = POOL.get_str(s).unwrap();
138
139        assert_eq!(cstr.to_str().unwrap(), s);
140    }
141
142
143    #[test]
144    #[should_panic]
145    fn bad_string() {
146        let s = "fo\0o";
147        let _cstr = POOL.get_str(s).unwrap();
148    }
149}