@@ -12,13 +12,13 @@ use std::ffi::{c_char, CString};
1212
1313fn convert_char_slice_to_bytes_string ( slice : CharSlice ) -> BytesString {
1414 // TODO: Strip the invalid bytes in the tracer instead
15- unsafe {
16- match String :: from_utf8_lossy ( slice . as_bytes ( ) ) {
17- Cow :: Owned ( s ) => s . into ( ) ,
18- Cow :: Borrowed ( _ ) => {
19- BytesString :: from_bytes_unchecked ( Bytes :: from_underlying ( slice. as_bytes ( ) . to_vec ( ) ) )
20- }
21- }
15+ match String :: from_utf8_lossy ( slice . as_bytes ( ) ) {
16+ Cow :: Owned ( s ) => s . into ( ) ,
17+ Cow :: Borrowed ( _ ) => unsafe {
18+ // Safety: if `from_utf8_lossy` returns a borrowed `str`, the latter can't be anything
19+ // else than the original slice which is thus valid UTF8.
20+ BytesString :: from_bytes_unchecked ( Bytes :: from_underlying ( slice . as_bytes ( ) . to_vec ( ) ) )
21+ } ,
2222 }
2323}
2424
@@ -31,8 +31,10 @@ fn set_string_field(field: &mut BytesString, slice: CharSlice) {
3131}
3232
3333#[ inline]
34- fn get_string_field ( field : & BytesString ) -> CharSlice < ' static > {
34+ fn get_string_field ( field : & BytesString ) -> CharSlice < ' _ > {
3535 let string = field. as_str ( ) ;
36+ // Safety: `field` is a `ByteString`, which guarantees it is backed by valid UTF8 bytes. The
37+ // lifetime of the returned slice makes sure it borrows from `field`.
3638 unsafe { CharSlice :: from_raw_parts ( string. as_ptr ( ) . cast ( ) , string. len ( ) ) }
3739}
3840
@@ -57,33 +59,39 @@ fn exists_hashmap<V>(map: &HashMap<BytesString, V>, key: CharSlice) -> bool {
5759 map. contains_key ( & bytes_str_key)
5860}
5961
60- #[ allow( clippy:: missing_safety_doc) ]
61- unsafe fn get_hashmap_keys < V > (
62- map : & HashMap < BytesString , V > ,
62+ /// The return value is an owned array of slices (`Box<[CharSlice<'a>]>`) that must be dropped
63+ /// explicitly.
64+ fn get_hashmap_keys < ' a , V > (
65+ map : & ' a HashMap < BytesString , V > ,
6366 out_count : & mut usize ,
64- ) -> * mut CharSlice < ' static > {
67+ ) -> * mut CharSlice < ' a > {
6568 let mut keys: Vec < & str > = map. keys ( ) . map ( |b| b. as_str ( ) ) . collect ( ) ;
6669 keys. sort_unstable ( ) ;
6770
68- let mut slices = Vec :: with_capacity ( keys. len ( ) ) ;
69- for key in keys {
70- slices. push ( CharSlice :: from_raw_parts ( key. as_ptr ( ) . cast ( ) , key. len ( ) ) ) ;
71- }
71+ let slices: Box < [ CharSlice ] > = keys
72+ . iter ( )
73+ . map ( |key| {
74+ // Safety: `BytesString` enforces that the content is valid UTF8. We returne a slice
75+ // with lifetime `'a`, which guarantees that the underlying allocation will remain live
76+ // and immutable for the lifetime of the slice.
77+ unsafe { CharSlice :: from_raw_parts ( key. as_ptr ( ) . cast ( ) , key. len ( ) ) }
78+ } )
79+ . collect ( ) ;
7280
7381 * out_count = slices. len ( ) ;
74- Box :: into_raw ( slices. into_boxed_slice ( ) ) as * mut CharSlice < ' static >
82+ Box :: into_raw ( slices) as * mut CharSlice < ' a >
7583}
7684
77- #[ allow( clippy:: missing_safety_doc) ]
78- unsafe fn new_vector_item < T : Default > ( vec : & mut Vec < T > ) -> & mut T {
85+ fn new_vector_item < T : Default > ( vec : & mut Vec < T > ) -> & mut T {
7986 vec. push ( T :: default ( ) ) ;
80- vec. last_mut ( ) . unwrap_unchecked ( )
87+ // Safety: we just pushed a value to the vector, so `last_mut()` returns `Some`
88+ unsafe { vec. last_mut ( ) . unwrap_unchecked ( ) }
8189}
8290
83- #[ allow( clippy:: missing_safety_doc) ]
84- unsafe fn new_vector_push < T > ( vec : & mut Vec < T > , el : T ) -> & mut T {
91+ fn new_vector_push < T > ( vec : & mut Vec < T > , el : T ) -> & mut T {
8592 vec. push ( el) ;
86- vec. last_mut ( ) . unwrap_unchecked ( )
93+ // Safety: we just pushed a value to the vector, so `last_mut()` returns `Some`
94+ unsafe { vec. last_mut ( ) . unwrap_unchecked ( ) }
8795}
8896
8997#[ no_mangle]
@@ -130,20 +138,20 @@ pub extern "C" fn ddog_get_traces_size(traces: &TracesBytes) -> usize {
130138 traces. len ( )
131139}
132140
133- #[ no_mangle]
134- pub extern "C" fn ddog_get_trace ( traces : & mut TracesBytes , index : usize ) -> * mut TraceBytes {
135- if index >= traces. len ( ) {
136- return std:: ptr:: null_mut ( ) ;
137- }
141+ // Note that per [RFC3391](https://rust-lang.github.io/rfcs/3391-result_ffi_guarantees.html), we
142+ // can return values as `Option<&/&mut>` that will have the same ABI `*const/*mut TraceBytes`, with
143+ // `None` corresponding to `null`.
138144
139- unsafe { traces. get_unchecked_mut ( index) as * mut TraceBytes }
145+ #[ no_mangle]
146+ pub extern "C" fn ddog_get_trace ( traces : & mut TracesBytes , index : usize ) -> Option < & TraceBytes > {
147+ traces. get ( index)
140148}
141149
142150// ------------------ TraceBytes ------------------
143151
144152#[ no_mangle]
145153pub extern "C" fn ddog_traces_new_trace ( traces : & mut TracesBytes ) -> & mut TraceBytes {
146- unsafe { new_vector_item ( traces) }
154+ new_vector_item ( traces)
147155}
148156
149157#[ no_mangle]
@@ -152,19 +160,15 @@ pub extern "C" fn ddog_get_trace_size(trace: &TraceBytes) -> usize {
152160}
153161
154162#[ no_mangle]
155- pub extern "C" fn ddog_get_span ( trace : & mut TraceBytes , index : usize ) -> * mut SpanBytes {
156- if index >= trace. len ( ) {
157- return std:: ptr:: null_mut ( ) ;
158- }
159-
160- unsafe { trace. get_unchecked_mut ( index) as * mut SpanBytes }
163+ pub extern "C" fn ddog_get_span ( trace : & mut TraceBytes , index : usize ) -> Option < & SpanBytes > {
164+ trace. get ( index)
161165}
162166
163167// ------------------- SpanBytes -------------------
164168
165169#[ no_mangle]
166170pub extern "C" fn ddog_trace_new_span ( trace : & mut TraceBytes ) -> & mut SpanBytes {
167- unsafe { new_vector_item ( trace) }
171+ new_vector_item ( trace)
168172}
169173
170174#[ no_mangle]
@@ -173,40 +177,46 @@ pub extern "C" fn ddog_trace_new_span_with_capacities(
173177 meta_size : usize ,
174178 metrics_size : usize ,
175179) -> & mut SpanBytes {
176- unsafe {
177- new_vector_push (
178- trace,
179- SpanBytes {
180- meta : HashMap :: with_capacity ( meta_size) ,
181- metrics : HashMap :: with_capacity ( metrics_size) ,
182- ..SpanBytes :: default ( )
183- } ,
184- )
185- }
180+ new_vector_push (
181+ trace,
182+ SpanBytes {
183+ meta : HashMap :: with_capacity ( meta_size) ,
184+ metrics : HashMap :: with_capacity ( metrics_size) ,
185+ ..SpanBytes :: default ( )
186+ } ,
187+ )
186188}
187189
190+ /// The returned slice is an owned allocation that must be properly freed using
191+ /// [`ddog_free_charslice`].
188192#[ no_mangle]
189193pub extern "C" fn ddog_span_debug_log ( span : & SpanBytes ) -> CharSlice < ' static > {
190- unsafe {
191- let debug_str = format ! ( "{span:?}" ) ;
192- let len = debug_str. len ( ) ;
193- let cstring = CString :: new ( debug_str) . unwrap_or_default ( ) ;
194+ let debug_str = format ! ( "{span:?}" ) ;
195+ let len = debug_str. len ( ) ;
196+ let cstring = CString :: new ( debug_str) . unwrap_or_default ( ) ;
194197
195- CharSlice :: from_raw_parts ( cstring . into_raw ( ) . cast ( ) , len )
196- }
198+ // Safety: `CString` is an owned, valid UTF8 string.
199+ unsafe { CharSlice :: from_raw_parts ( cstring . into_raw ( ) . cast ( ) , len ) }
197200}
198201
202+ /// Frees an owned [`CharSlice`]. Note that some functions of this API return borrowed slices that
203+ /// must NOT be freed. Only a few selected functions return slices that must be freed, and this is
204+ /// mentioned explicitly in their documentation.
205+ ///
206+ /// # Safety
207+ ///
208+ /// `slice` must be an owned char slice that has been returned by one of the functions of this API.
199209#[ no_mangle]
200- pub extern "C" fn ddog_free_charslice ( slice : CharSlice < ' static > ) {
210+ pub unsafe extern "C" fn ddog_free_charslice ( slice : CharSlice < ' static > ) {
201211 let ( ptr, len) = slice. as_raw_parts ( ) ;
202212
203213 if len == 0 || ptr. is_null ( ) {
204214 return ;
205215 }
206216
217+ // Safety: the owned char slices returned by functions of this API are `Box`-allocated.
207218 unsafe {
208- let owned_ptr = ptr as * mut c_char ;
209- let _ = Box :: from_raw ( owned_ptr) ;
219+ let _ = Box :: from_raw ( ptr as * mut c_char ) ;
210220 }
211221}
212222
@@ -216,7 +226,7 @@ pub extern "C" fn ddog_set_span_service(span: &mut SpanBytes, slice: CharSlice)
216226}
217227
218228#[ no_mangle]
219- pub extern "C" fn ddog_get_span_service ( span : & mut SpanBytes ) -> CharSlice < ' static > {
229+ pub extern "C" fn ddog_get_span_service ( span : & mut SpanBytes ) -> CharSlice < ' _ > {
220230 get_string_field ( & span. service )
221231}
222232
@@ -226,7 +236,7 @@ pub extern "C" fn ddog_set_span_name(span: &mut SpanBytes, slice: CharSlice) {
226236}
227237
228238#[ no_mangle]
229- pub extern "C" fn ddog_get_span_name ( span : & mut SpanBytes ) -> CharSlice < ' static > {
239+ pub extern "C" fn ddog_get_span_name ( span : & mut SpanBytes ) -> CharSlice < ' _ > {
230240 get_string_field ( & span. name )
231241}
232242
@@ -236,7 +246,7 @@ pub extern "C" fn ddog_set_span_resource(span: &mut SpanBytes, slice: CharSlice)
236246}
237247
238248#[ no_mangle]
239- pub extern "C" fn ddog_get_span_resource ( span : & mut SpanBytes ) -> CharSlice < ' static > {
249+ pub extern "C" fn ddog_get_span_resource ( span : & mut SpanBytes ) -> CharSlice < ' _ > {
240250 get_string_field ( & span. resource )
241251}
242252
@@ -246,7 +256,7 @@ pub extern "C" fn ddog_set_span_type(span: &mut SpanBytes, slice: CharSlice) {
246256}
247257
248258#[ no_mangle]
249- pub extern "C" fn ddog_get_span_type ( span : & mut SpanBytes ) -> CharSlice < ' static > {
259+ pub extern "C" fn ddog_get_span_type ( span : & mut SpanBytes ) -> CharSlice < ' _ > {
250260 get_string_field ( & span. r#type )
251261}
252262
@@ -325,9 +335,15 @@ pub extern "C" fn ddog_del_span_meta(span: &mut SpanBytes, key: CharSlice) {
325335}
326336
327337#[ no_mangle]
328- pub extern "C" fn ddog_get_span_meta ( span : & mut SpanBytes , key : CharSlice ) -> CharSlice < ' static > {
338+ pub extern "C" fn ddog_get_span_meta < ' a > (
339+ span : & ' a mut SpanBytes ,
340+ key : CharSlice < ' _ > ,
341+ ) -> CharSlice < ' a > {
329342 let bytes_str_key = convert_char_slice_to_bytes_string ( key) ;
330343 match span. meta . get ( & bytes_str_key) {
344+ // Safety: value is a `ByteString`, which guarantees it is valid UTF8. We return a slice
345+ // that borrows from `span`, making sure it remains alive and immutable for the lifetime of
346+ // the slice.
331347 Some ( value) => unsafe {
332348 CharSlice :: from_raw_parts ( value. as_str ( ) . as_ptr ( ) . cast ( ) , value. as_str ( ) . len ( ) )
333349 } ,
@@ -340,12 +356,14 @@ pub extern "C" fn ddog_has_span_meta(span: &mut SpanBytes, key: CharSlice) -> bo
340356 exists_hashmap ( & span. meta , key)
341357}
342358
359+ /// The return value is an owned array of slices (`Box<[CharSlice]>`) that must be freed explicitly
360+ /// through [`ddog_span_free_keys_ptr`].
343361#[ no_mangle]
344- pub extern "C" fn ddog_span_meta_get_keys (
345- span : & mut SpanBytes ,
362+ pub extern "C" fn ddog_span_meta_get_keys < ' a > (
363+ span : & ' a mut SpanBytes ,
346364 out_count : & mut usize ,
347- ) -> * mut CharSlice < ' static > {
348- unsafe { get_hashmap_keys ( & span. meta , out_count) }
365+ ) -> * mut CharSlice < ' a > {
366+ get_hashmap_keys ( & span. meta , out_count)
349367}
350368
351369#[ no_mangle]
@@ -380,11 +398,11 @@ pub extern "C" fn ddog_has_span_metrics(span: &mut SpanBytes, key: CharSlice) ->
380398}
381399
382400#[ no_mangle]
383- pub extern "C" fn ddog_span_metrics_get_keys (
384- span : & mut SpanBytes ,
401+ pub extern "C" fn ddog_span_metrics_get_keys < ' a > (
402+ span : & ' a mut SpanBytes ,
385403 out_count : & mut usize ,
386- ) -> * mut CharSlice < ' static > {
387- unsafe { get_hashmap_keys ( & span. metrics , out_count) }
404+ ) -> * mut CharSlice < ' a > {
405+ get_hashmap_keys ( & span. metrics , out_count)
388406}
389407
390408#[ no_mangle]
@@ -402,12 +420,15 @@ pub extern "C" fn ddog_del_span_meta_struct(span: &mut SpanBytes, key: CharSlice
402420}
403421
404422#[ no_mangle]
405- pub extern "C" fn ddog_get_span_meta_struct (
406- span : & mut SpanBytes ,
407- key : CharSlice ,
408- ) -> CharSlice < ' static > {
423+ pub extern "C" fn ddog_get_span_meta_struct < ' a > (
424+ span : & ' a mut SpanBytes ,
425+ key : CharSlice < ' _ > ,
426+ ) -> CharSlice < ' a > {
409427 let bytes_str_key = convert_char_slice_to_bytes_string ( key) ;
410428 match span. meta_struct . get ( & bytes_str_key) {
429+ // Safety: value is a `ByteString`, which guarantees it is valid UTF8. We return a slice
430+ // that borrows from `span`, ensuring it remains alive and unchanged for the lifetime of
431+ // the slice.
411432 Some ( value) => unsafe { CharSlice :: from_raw_parts ( value. as_ptr ( ) . cast ( ) , value. len ( ) ) } ,
412433 None => CharSlice :: empty ( ) ,
413434 }
@@ -418,29 +439,39 @@ pub extern "C" fn ddog_has_span_meta_struct(span: &mut SpanBytes, key: CharSlice
418439 exists_hashmap ( & span. meta_struct , key)
419440}
420441
442+ /// The return value is an array of slices (`Box<[CharSlice]>`) that must be freed explicitly
443+ /// through [`ddog_span_free_keys_ptr`].
421444#[ no_mangle]
422- pub extern "C" fn ddog_span_meta_struct_get_keys (
423- span : & mut SpanBytes ,
445+ pub extern "C" fn ddog_span_meta_struct_get_keys < ' a > (
446+ span : & ' a mut SpanBytes ,
424447 out_count : & mut usize ,
425- ) -> * mut CharSlice < ' static > {
426- unsafe { get_hashmap_keys ( & span. meta_struct , out_count) }
448+ ) -> * mut CharSlice < ' a > {
449+ get_hashmap_keys ( & span. meta_struct , out_count)
427450}
428451
452+ /// # Safety
453+ ///
454+ /// `keys_ptr` must have been returned by one of the `ddog_xxx_get_keys()` functions, and must not
455+ /// have been already freed.
429456#[ no_mangle]
430- #[ allow( clippy:: missing_safety_doc) ]
431- pub unsafe extern "C" fn ddog_span_free_keys_ptr ( keys_ptr : * mut CharSlice < ' static > , count : usize ) {
457+ pub unsafe extern "C" fn ddog_span_free_keys_ptr ( keys_ptr : * mut CharSlice < ' _ > , count : usize ) {
432458 if keys_ptr. is_null ( ) || count == 0 {
433459 return ;
434460 }
435461
436- Vec :: from_raw_parts ( keys_ptr, count, count) ;
462+ // Safety: all `xxx_get_keys()` functions return from `get_hashmap_keys()`, which returns a
463+ // `Box<[T]>`. It is an official guarantee of `Vec` that this can be freely converted to and
464+ // from `Box<[T]>` when `len == capacity`.
465+ unsafe {
466+ Vec :: from_raw_parts ( keys_ptr, count, count) ;
467+ }
437468}
438469
439470// ------------------- SpanLinkBytes -------------------
440471
441472#[ no_mangle]
442473pub extern "C" fn ddog_span_new_link ( span : & mut SpanBytes ) -> & mut SpanLinkBytes {
443- unsafe { new_vector_item ( & mut span. span_links ) }
474+ new_vector_item ( & mut span. span_links )
444475}
445476
446477#[ no_mangle]
@@ -485,7 +516,7 @@ pub extern "C" fn ddog_add_link_attributes(
485516
486517#[ no_mangle]
487518pub extern "C" fn ddog_span_new_event ( span : & mut SpanBytes ) -> & mut SpanEventBytes {
488- unsafe { new_vector_item ( & mut span. span_events ) }
519+ new_vector_item ( & mut span. span_events )
489520}
490521
491522#[ no_mangle]
@@ -540,6 +571,8 @@ pub extern "C" fn ddog_add_event_attributes_float(
540571
541572// ------------------- Export Functions -------------------
542573
574+ /// The returned slice is an owned allocation that must be properly freed using
575+ /// [`ddog_free_charslice`].
543576#[ no_mangle]
544577pub extern "C" fn ddog_serialize_trace_into_charslice (
545578 trace : & mut TraceBytes ,
0 commit comments