@@ -80,3 +80,155 @@ macro_rules! ffi_try_impl {
8080 result
8181 } } ;
8282}
83+
84+ /// Value which can be converted into a C string.
85+ ///
86+ /// The trait is used as argument to functions which wish to accept either
87+ /// [`&str`] or [`&CStr`] arguments while internally need to interact with
88+ /// C APIs. Accepting [`&str`] may be more convenient for users but requires
89+ /// conversion into [`CString`] internally which requires allocation. With this
90+ /// trait, latency-conscious users may choose to prepare `CStr` in advance and
91+ /// then pass it directly without having to incur the conversion cost.
92+ ///
93+ /// To use the trait, function should accept `impl CStrLike` and after baking
94+ /// the argument (with [`CStrLike::bake`] method) it can use it as a `&CStr`
95+ /// (since the baked result dereferences into `CStr`).
96+ ///
97+ /// # Example
98+ ///
99+ /// ```
100+ /// use std::ffi::{CStr, CString};
101+ /// use rocksdb::CStrLike;
102+ ///
103+ /// fn strlen(arg: impl CStrLike) -> std::result::Result<usize, String> {
104+ /// let baked = arg.bake().map_err(|err| err.to_string())?;
105+ /// Ok(unsafe { libc::strlen(baked.as_ptr()) })
106+ /// }
107+ ///
108+ /// const FOO: &str = "foo";
109+ /// const BAR: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"bar\0") };
110+ ///
111+ /// assert_eq!(Ok(3), strlen(FOO));
112+ /// assert_eq!(Ok(3), strlen(BAR));
113+ /// ```
114+ pub trait CStrLike {
115+ type Baked : std:: ops:: Deref < Target = CStr > ;
116+ type Error : std:: fmt:: Debug + std:: fmt:: Display ;
117+
118+ /// Bakes self into value which can be freely converted into [`&CStr`].
119+ ///
120+ /// This may require allocation and may fail if `self` has invalid value.
121+ fn bake ( self ) -> Result < Self :: Baked , Self :: Error > ;
122+
123+ /// Consumers and converts value into an owned [`CString`].
124+ ///
125+ /// If `Self` is already a `CString` simply returns it; if it’s a reference
126+ /// to a `CString` then the value is cloned. In other cases this may
127+ /// require allocation and may fail if `self` has invalid value.
128+ fn into_c_string ( self ) -> Result < CString , Self :: Error > ;
129+ }
130+
131+ impl CStrLike for & str {
132+ type Baked = CString ;
133+ type Error = std:: ffi:: NulError ;
134+
135+ fn bake ( self ) -> Result < Self :: Baked , Self :: Error > {
136+ CString :: new ( self )
137+ }
138+ fn into_c_string ( self ) -> Result < CString , Self :: Error > {
139+ CString :: new ( self )
140+ }
141+ }
142+
143+ // This is redundant for the most part and exists so that `foo(&string)` (where
144+ // `string: String` works just as if `foo` took `arg: &str` argument.
145+ impl CStrLike for & String {
146+ type Baked = CString ;
147+ type Error = std:: ffi:: NulError ;
148+
149+ fn bake ( self ) -> Result < Self :: Baked , Self :: Error > {
150+ CString :: new ( self . as_bytes ( ) )
151+ }
152+ fn into_c_string ( self ) -> Result < CString , Self :: Error > {
153+ CString :: new ( self . as_bytes ( ) )
154+ }
155+ }
156+
157+ impl CStrLike for & CStr {
158+ type Baked = Self ;
159+ type Error = std:: convert:: Infallible ;
160+
161+ fn bake ( self ) -> Result < Self :: Baked , Self :: Error > {
162+ Ok ( self )
163+ }
164+ fn into_c_string ( self ) -> Result < CString , Self :: Error > {
165+ Ok ( self . to_owned ( ) )
166+ }
167+ }
168+
169+ // This exists so that if caller constructs a `CString` they can pass it into
170+ // the function accepting `CStrLike` argument. Some of such functions may take
171+ // the argument whereas otherwise they would need to allocated a new owned
172+ // object.
173+ impl CStrLike for CString {
174+ type Baked = CString ;
175+ type Error = std:: convert:: Infallible ;
176+
177+ fn bake ( self ) -> Result < Self :: Baked , Self :: Error > {
178+ Ok ( self )
179+ }
180+ fn into_c_string ( self ) -> Result < CString , Self :: Error > {
181+ Ok ( self )
182+ }
183+ }
184+
185+ // This is redundant for the most part and exists so that `foo(&cstring)` (where
186+ // `string: CString` works just as if `foo` took `arg: &CStr` argument.
187+ impl < ' a > CStrLike for & ' a CString {
188+ type Baked = & ' a CStr ;
189+ type Error = std:: convert:: Infallible ;
190+
191+ fn bake ( self ) -> Result < Self :: Baked , Self :: Error > {
192+ Ok ( self )
193+ }
194+ fn into_c_string ( self ) -> Result < CString , Self :: Error > {
195+ Ok ( self . clone ( ) )
196+ }
197+ }
198+
199+ #[ test]
200+ fn test_c_str_like_bake ( ) {
201+ fn test < S : CStrLike > ( value : S ) -> Result < usize , S :: Error > {
202+ value
203+ . bake ( )
204+ . map ( |value| unsafe { libc:: strlen ( value. as_ptr ( ) ) } )
205+ }
206+
207+ assert_eq ! ( Ok ( 3 ) , test( "foo" ) ) ; // &str
208+ assert_eq ! ( Ok ( 3 ) , test( & String :: from( "foo" ) ) ) ; // String
209+ assert_eq ! ( Ok ( 3 ) , test( CString :: new( "foo" ) . unwrap( ) . as_ref( ) ) ) ; // &CStr
210+ assert_eq ! ( Ok ( 3 ) , test( & CString :: new( "foo" ) . unwrap( ) ) ) ; // &CString
211+ assert_eq ! ( Ok ( 3 ) , test( CString :: new( "foo" ) . unwrap( ) ) ) ; // CString
212+
213+ assert_eq ! ( 3 , test( "foo\0 bar" ) . err( ) . unwrap( ) . nul_position( ) ) ;
214+ }
215+
216+ #[ test]
217+ fn test_c_str_like_into ( ) {
218+ fn test < S : CStrLike > ( value : S ) -> Result < CString , S :: Error > {
219+ value. into_c_string ( )
220+ }
221+
222+ let want = CString :: new ( "foo" ) . unwrap ( ) ;
223+
224+ assert_eq ! ( Ok ( want. clone( ) ) , test( "foo" ) ) ; // &str
225+ assert_eq ! ( Ok ( want. clone( ) ) , test( & String :: from( "foo" ) ) ) ; // &String
226+ assert_eq ! (
227+ Ok ( want. clone( ) ) ,
228+ test( CString :: new( "foo" ) . unwrap( ) . as_ref( ) )
229+ ) ; // &CStr
230+ assert_eq ! ( Ok ( want. clone( ) ) , test( & CString :: new( "foo" ) . unwrap( ) ) ) ; // &CString
231+ assert_eq ! ( Ok ( want) , test( CString :: new( "foo" ) . unwrap( ) ) ) ; // CString
232+
233+ assert_eq ! ( 3 , test( "foo\0 bar" ) . err( ) . unwrap( ) . nul_position( ) ) ;
234+ }
0 commit comments