Skip to content

Commit ebb3880

Browse files
committed
docs: expand listing-004
1 parent ccd889e commit ebb3880

File tree

1 file changed

+50
-11
lines changed

1 file changed

+50
-11
lines changed

listing-004.md

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
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
77
unsafe 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
5068
unsafe 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
5977
static 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
7694
unsafe 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
91109
unsafe 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
113131
unsafe 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
132150
unsafe 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
146164
unsafe 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
156174
unsafe 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
166184
unsafe 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

Comments
 (0)