11# Listing 004
22
3- This section unpacks how the Rust extension manages the recorder’s lifecycle and begins translating Ruby objects into traceable Rust structures. We examine ` gems/codetracer-ruby-recorder/ext/native_tracer/src/lib.rs ` around the helper for struct serialization, the FFI hooks that allocate and enable the recorder, and the start of ` to_value ` which handles primitive Ruby types .
3+ This listing follows the Rust half of the recorder, showing how the extension allocates and frees the ` Recorder ` , toggles Ruby's trace hooks, and begins translating primitive Ruby values. All snippets come from ` gems/codetracer-ruby-recorder/ext/native_tracer/src/lib.rs ` .
44
5- ** Serialize a Ruby struct into a typed runtime-tracing record, computing field type IDs and assigning a versioned name .**
5+ ** Signature for ` struct_value ` collects the recorder context, struct metadata, and a recursion depth .**
66``` rust
77unsafe fn struct_value (
88 recorder : & mut Recorder ,
@@ -11,24 +11,39 @@ unsafe fn struct_value(
1111 field_values : & [VALUE ],
1212 depth : usize ,
1313) -> ValueRecord {
14+ ```
15+
16+ ** Allocate space for converted fields and recursively map each Ruby field to a ` ValueRecord ` .**
17+ ``` rust
1418 let mut vals = Vec :: with_capacity (field_values . len ());
1519 for & v in field_values {
1620 vals . push (to_value (recorder , v , depth - 1 ));
1721 }
22+ ```
1823
24+ ** Track a monotonically increasing version number per struct name.**
25+ ``` rust
1926 let version_entry = recorder
2027 . struct_type_versions
2128 . entry (class_name . to_string ())
2229 . or_insert (0 );
2330 let name_version = format! (" {} (#{})" , class_name , * version_entry );
2431 * version_entry += 1 ;
32+ ```
33+
34+ ** Describe each field by name and the type ID of its converted value.**
35+ ``` rust
2536 let mut field_types = Vec :: with_capacity (field_names . len ());
2637 for (n , v ) in field_names . iter (). zip (& vals ) {
2738 field_types . push (FieldTypeRecord {
2839 name : (* n ). to_string (),
2940 type_id : value_type_id (v ),
3041 });
3142 }
43+ ```
44+
45+ ** Assemble a ` TypeRecord ` for the struct and register it with the trace writer to obtain a type ID.**
46+ ``` rust
3247 let typ = TypeRecord {
3348 kind : TypeKind :: Struct ,
3449 lang_type : name_version ,
@@ -37,15 +52,18 @@ unsafe fn struct_value(
3752 },
3853 };
3954 let type_id = TraceWriter :: ensure_raw_type_id (& mut * recorder . tracer, typ );
55+ ```
4056
57+ ** Return a structured value with its field data and associated type ID.**
58+ ``` rust
4159 ValueRecord :: Struct {
4260 field_values : vals ,
4361 type_id ,
4462 }
4563}
4664```
4765
48- ** Custom destructor frees the Rust ` Recorder ` when Ruby’s GC releases the wrapper object .**
66+ ** ` recorder_free ` is registered as a destructor and drops the boxed recorder when the Ruby object is garbage collected .**
4967``` rust
5068unsafe extern " C" fn recorder_free (ptr : * mut c_void ) {
5169 if ! ptr . is_null () {
@@ -54,7 +72,7 @@ unsafe extern "C" fn recorder_free(ptr: *mut c_void) {
5472}
5573```
5674
57- ** Declare Ruby’s view of the ` Recorder ` data type, wiring in the free callback for GC .**
75+ ** ` RECORDER_TYPE ` exposes the recorder to Ruby, naming the type and specifying the ` dfree ` callback.**
5876``` rust
5977static mut RECORDER_TYPE : rb_data_type_t = rb_data_type_t {
6078 wrap_struct_name : b " Recorder\ 0" . as_ptr () as * const c_char ,
@@ -71,7 +89,7 @@ static mut RECORDER_TYPE: rb_data_type_t = rb_data_type_t {
7189};
7290```
7391
74- ** Fetch the internal ` Recorder ` pointer from a Ruby object, raising ` IOError ` if the type does not match .**
92+ ** ` get_recorder ` fetches the internal pointer from a Ruby object, raising ` IOError ` if the type check fails .**
7593``` rust
7694unsafe fn get_recorder (obj : VALUE ) -> * mut Recorder {
7795 let ty = std :: ptr :: addr_of! (RECORDER_TYPE ) as * const rb_data_type_t ;
@@ -86,7 +104,7 @@ unsafe fn get_recorder(obj: VALUE) -> *mut Recorder {
86104}
87105```
88106
89- ** Allocator for the Ruby class instantiates a boxed ` Recorder ` with default type IDs and inactive state .**
107+ ** Allocator for the Ruby class constructs a fresh ` Recorder ` with default type IDs and inactive tracing .**
90108``` rust
91109unsafe extern " C" fn ruby_recorder_alloc (klass : VALUE ) -> VALUE {
92110 let recorder = Box :: new (Recorder {
@@ -108,7 +126,7 @@ unsafe extern "C" fn ruby_recorder_alloc(klass: VALUE) -> VALUE {
108126}
109127```
110128
111- ** Enable tracing by registering a low-level event hook; only one hook is active at a time .**
129+ ** ` enable_tracing ` attaches a raw event hook so Ruby invokes our callback on line, call, return, and raise events .**
112130``` rust
113131unsafe extern " C" fn enable_tracing (self_val : VALUE ) -> VALUE {
114132 let recorder = & mut * get_recorder (self_val );
@@ -127,7 +145,7 @@ unsafe extern "C" fn enable_tracing(self_val: VALUE) -> VALUE {
127145}
128146```
129147
130- ** Disable tracing by removing the previously installed event hook .**
148+ ** ` disable_tracing ` removes that hook and marks the recorder inactive .**
131149``` rust
132150unsafe extern " C" fn disable_tracing (self_val : VALUE ) -> VALUE {
133151 let recorder = & mut * get_recorder (self_val );
@@ -141,7 +159,7 @@ unsafe extern "C" fn disable_tracing(self_val: VALUE) -> VALUE {
141159}
142160```
143161
144- ** Helper that converts a C string pointer to a Rust ` String ` , returning ` None ` if null.**
162+ ** ` cstr_to_string ` converts a C string pointer to a Rust ` String ` , returning ` None ` when the pointer is null.**
145163``` rust
146164unsafe fn cstr_to_string (ptr : * const c_char ) -> Option <String > {
147165 if ptr . is_null () {
@@ -151,7 +169,7 @@ unsafe fn cstr_to_string(ptr: *const c_char) -> Option<String> {
151169}
152170```
153171
154- ** Extract a UTF‑8 string from a Ruby ` VALUE ` , replacing invalid bytes .**
172+ ** ` rstring_lossy ` reads a Ruby ` String ` 's raw bytes and builds a UTF‑8 string , replacing invalid sequences .**
155173``` rust
156174unsafe fn rstring_lossy (val : VALUE ) -> String {
157175 let ptr = RSTRING_PTR (val );
@@ -161,7 +179,7 @@ unsafe fn rstring_lossy(val: VALUE) -> String {
161179}
162180```
163181
164- ** Beginning of ` to_value ` : limit depth, map ` nil ` and booleans, and convert integers and floats with cached type IDs .**
182+ ** ` to_value ` begins value translation, first enforcing a recursion limit and checking for ` nil ` .**
165183``` rust
166184unsafe fn to_value (recorder : & mut Recorder , val : VALUE , depth : usize ) -> ValueRecord {
167185 if depth == 0 {
@@ -174,19 +192,31 @@ unsafe fn to_value(recorder: &mut Recorder, val: VALUE, depth: usize) -> ValueRe
174192 type_id : recorder . error_type_id,
175193 };
176194 }
195+ ```
196+
197+ ** Booleans map to ` Bool ` records, distinguishing ` true ` from ` false ` and reusing a cached type ID.**
198+ ``` rust
177199 if val == (Qtrue as VALUE ) || val == (Qfalse as VALUE ) {
178200 return ValueRecord :: Bool {
179201 b : val == (Qtrue as VALUE ),
180202 type_id : recorder . bool_type_id,
181203 };
182204 }
205+ ```
206+
207+ ** Integers become ` Int ` records holding the numeric value and its type ID.**
208+ ``` rust
183209 if RB_INTEGER_TYPE_P (val ) {
184210 let i = rb_num2long (val ) as i64 ;
185211 return ValueRecord :: Int {
186212 i ,
187213 type_id : recorder . int_type_id,
188214 };
189215 }
216+ ```
217+
218+ ** For floats, lazily register the ` Float ` type and then store the numeric value with the obtained ID.**
219+ ``` rust
190220 if RB_FLOAT_TYPE_P (val ) {
191221 let f = rb_num2dbl (val );
192222 let type_id = if recorder . float_type_id == runtime_tracing :: NONE_TYPE_ID {
@@ -198,16 +228,25 @@ unsafe fn to_value(recorder: &mut Recorder, val: VALUE, depth: usize) -> ValueRe
198228 };
199229 return ValueRecord :: Float { f , type_id };
200230 }
231+ ```
232+
233+ ** Symbols are encoded as strings using their interned names and the cached symbol type ID.**
234+ ``` rust
201235 if RB_SYMBOL_P (val ) {
202236 return ValueRecord :: String {
203237 text : cstr_to_string (rb_id2name (rb_sym2id (val ))). unwrap_or_default (),
204238 type_id : recorder . symbol_type_id,
205239 };
206240 }
241+ ```
242+
243+ ** Finally, Ruby ` String ` objects are copied lossily into UTF‑8 and tagged with the string type ID.**
244+ ``` rust
207245 if RB_TYPE_P (val , rb_sys :: ruby_value_type :: RUBY_T_STRING ) {
208246 return ValueRecord :: String {
209247 text : rstring_lossy (val ),
210248 type_id : recorder . string_type_id,
211249 };
212250 }
251+ }
213252```
0 commit comments