1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
// SPDX-License-Identifier: Apache-2.0
#![allow(dead_code)]
//! Mark-and-sweep GC stub — demonstrates the two-phase collection cycle using
//! object indices instead of actual heap pointers.
/// Identifier for a GC-managed object.
pub type GcId = usize;
/// State of a GC object.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GcState {
White, /* unreachable — candidate for collection */
Grey, /* reachable but children not yet scanned */
Black, /* reachable and fully scanned */
}
/// A managed object with an adjacency list of references.
#[derive(Debug)]
pub struct GcObject {
pub id: GcId,
pub state: GcState,
pub refs: Vec<GcId>,
}
impl GcObject {
fn new(id: GcId) -> Self {
Self {
id,
state: GcState::White,
refs: Vec::new(),
}
}
}
/// Simple mark-and-sweep garbage collector stub.
pub struct GcStub {
objects: Vec<GcObject>,
roots: Vec<GcId>,
collected: usize,
}
impl GcStub {
/// Create an empty GC.
pub fn new() -> Self {
Self {
objects: Vec::new(),
roots: Vec::new(),
collected: 0,
}
}
/// Allocate a new managed object. Returns its id.
pub fn alloc(&mut self) -> GcId {
let id = self.objects.len();
self.objects.push(GcObject::new(id));
id
}
/// Add a reference from object `from` to object `to`.
pub fn add_ref(&mut self, from: GcId, to: GcId) {
if from < self.objects.len() {
self.objects[from].refs.push(to);
}
}
/// Mark `id` as a root (always reachable).
pub fn add_root(&mut self, id: GcId) {
self.roots.push(id);
}
/// Run mark phase: mark all objects reachable from roots.
pub fn mark(&mut self) {
/* reset all to white */
for obj in &mut self.objects {
obj.state = GcState::White;
}
/* seed grey set from roots */
let mut grey: Vec<GcId> = self.roots.clone();
for &r in &self.roots {
if r < self.objects.len() {
self.objects[r].state = GcState::Grey;
}
}
while let Some(id) = grey.pop() {
if id >= self.objects.len() {
continue;
}
let children: Vec<GcId> = self.objects[id].refs.clone();
for child in children {
if child < self.objects.len() && self.objects[child].state == GcState::White {
self.objects[child].state = GcState::Grey;
grey.push(child);
}
}
self.objects[id].state = GcState::Black;
}
}
/// Run sweep phase: remove all white objects and return count collected.
pub fn sweep(&mut self) -> usize {
let before = self.objects.len();
self.objects.retain(|o| o.state != GcState::White);
let freed = before - self.objects.len();
self.collected += freed;
freed
}
/// Full collection cycle (mark + sweep).
pub fn collect(&mut self) -> usize {
self.mark();
self.sweep()
}
/// Total number of live managed objects.
pub fn live_count(&self) -> usize {
self.objects.len()
}
/// Cumulative objects collected since creation.
pub fn total_collected(&self) -> usize {
self.collected
}
}
impl Default for GcStub {
fn default() -> Self {
Self::new()
}
}
/// Create a new GC stub.
pub fn new_gc_stub() -> GcStub {
GcStub::new()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_alloc() {
let mut gc = GcStub::new();
let id = gc.alloc();
assert_eq!(id, 0); /* first object has id 0 */
}
#[test]
fn test_collect_unreachable() {
let mut gc = GcStub::new();
gc.alloc(); /* unreachable object */
let freed = gc.collect();
assert_eq!(freed, 1); /* one object collected */
}
#[test]
fn test_root_not_collected() {
let mut gc = GcStub::new();
let id = gc.alloc();
gc.add_root(id);
let freed = gc.collect();
assert_eq!(freed, 0); /* root survives */
}
#[test]
fn test_reachable_via_root() {
let mut gc = GcStub::new();
let root = gc.alloc();
let child = gc.alloc();
gc.add_root(root);
gc.add_ref(root, child);
let freed = gc.collect();
assert_eq!(freed, 0); /* both reachable */
}
#[test]
fn test_unreachable_child() {
let mut gc = GcStub::new();
let root = gc.alloc();
let orphan = gc.alloc();
gc.add_root(root);
let _ = orphan;
let freed = gc.collect();
assert_eq!(freed, 1); /* orphan collected */
}
#[test]
fn test_live_count() {
let mut gc = GcStub::new();
let id = gc.alloc();
gc.add_root(id);
gc.collect();
assert_eq!(gc.live_count(), 1); /* root alive */
}
#[test]
fn test_total_collected() {
let mut gc = GcStub::new();
gc.alloc();
gc.alloc();
gc.collect();
assert_eq!(gc.total_collected(), 2); /* cumulative count */
}
#[test]
fn test_multiple_roots() {
let mut gc = GcStub::new();
let a = gc.alloc();
let b = gc.alloc();
gc.add_root(a);
gc.add_root(b);
let freed = gc.collect();
assert_eq!(freed, 0); /* both roots survive */
}
#[test]
fn test_default() {
let gc = GcStub::default();
assert_eq!(gc.live_count(), 0); /* default creates empty GC */
}
#[test]
fn test_new_helper() {
let gc = new_gc_stub();
assert_eq!(gc.live_count(), 0); /* helper creates empty GC */
}
}