Skip to main content

praxis_runtime/
var_cell.rs

1//! The `VarCell` value descriptor (§4.10).
2//!
3//! A `VarCell` is a single-slot GC heap cell holding one `GcRef`. It is the
4//! shared mutable storage for a `var` binding captured by a closure (§4.10:
5//! "mutable captures use GC-managed environment cells"). The binding site and
6//! every closure that captures the `var` refer to the *same* cell, so a write
7//! in one is visible in the other.
8//!
9//! A `var` is *boxed* into a `VarCell` at its binding site iff it is captured
10//! by some closure in the module (escape analysis, run during HIR lowering).
11//! Uncaptured `var`s stay as ordinary mutable locals — no cell overhead. The
12//! cell is transparent to the source program: reads (`Path`) deref it via
13//! `praxis_var_cell_get`, writes (`Assign`) store via `praxis_var_cell_set`.
14//!
15//! Per §5.5, `VarCell`s are never equatable or hashable — they are an internal
16//! implementation detail, not a first-class value the program can name.
17//!
18//! Its `TypeId` is derived from `BuiltinTypeId::VarCell`.
19
20use std::fmt::Write as _;
21
22use crate::GcRef;
23use crate::descriptor::{BuiltinTypeId, FormatSink, Tracer, TypeDescriptor};
24
25/// The runtime payload of a `VarCell`: a single `GcRef` slot. `#[repr(C)]` so
26/// the ABI wrappers can read/write it at a known offset.
27#[repr(C)]
28pub struct VarCellPayload {
29    /// The current value held by the captured `var`.
30    pub value: GcRef,
31}
32
33unsafe fn var_cell_trace(payload: *mut u8, tracer: &mut dyn Tracer) {
34    // SAFETY: caller guarantees `payload` points at an initialized VarCellPayload.
35    let p = unsafe { &*(payload as *const VarCellPayload) };
36    tracer.trace(p.value);
37}
38
39unsafe fn var_cell_drop(payload: *mut u8) {
40    // SAFETY: caller guarantees `payload` points at an initialized VarCellPayload.
41    // No heap allocation beyond the GcRef field (a plain pointer-sized Copy), so
42    // drop_in_place is a no-op; we still call it for uniformity with other payloads.
43    unsafe { std::ptr::drop_in_place(payload as *mut VarCellPayload) };
44}
45
46unsafe fn var_cell_format(payload: *const u8, out: &mut FormatSink<'_>) {
47    // SAFETY: caller guarantees `payload` points at an initialized VarCellPayload.
48    let _ = payload;
49    // VarCells are internal; render opaquely for debugging.
50    let _ = out.write_str("<var-cell>");
51}
52
53/// Descriptor for the `VarCell` internal value type (§4.10). Never equatable
54/// or hashable (it is not a first-class value).
55pub static VAR_CELL: TypeDescriptor = TypeDescriptor::builtin::<VarCellPayload>(
56    BuiltinTypeId::VarCell,
57    "VarCell",
58    var_cell_trace,
59    var_cell_drop,
60    var_cell_format,
61    None,
62    None,
63    // No container order: a `VarCell` is the compiler's box for a captured
64    // binding and never a user value, so it is never a key (ADR-138).
65    None,
66);
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn var_cell_descriptor_reports_capabilities() {
74        // VarCells are never equatable/hashable (internal type).
75        assert!(!VAR_CELL.is_equatable());
76        assert!(!VAR_CELL.is_hashable());
77        assert_eq!(VAR_CELL.name, "VarCell");
78        assert_eq!(VAR_CELL.as_builtin(), Some(BuiltinTypeId::VarCell));
79    }
80}