Skip to main content

cljrs_value/
native_object.rs

1//! The `NativeObject` trait for opaque Rust structs exposed as Clojure values.
2//!
3//! Unlike `Resource` (which uses `Arc` for deterministic `Drop`), native objects
4//! live behind `GcPtr` and participate in tracing GC.  They are intended for
5//! Rust values whose lifetime is managed by the garbage collector.
6
7use std::any::Any;
8use std::fmt;
9
10use cljrs_gc::{GcPtr, MarkVisitor, Trace};
11
12/// An opaque Rust value that can be wrapped as a Clojure `Value::NativeObject`.
13///
14/// Implementors must be `Send + Sync` (shared across threads), `Debug`
15/// (for display), and `Trace` (so the GC can walk any `GcPtr`/`Value`
16/// references held inside).
17///
18/// # Example
19///
20/// ```ignore
21/// struct Counter { n: AtomicI64 }
22///
23/// impl NativeObject for Counter {
24///     fn type_tag(&self) -> &str { "Counter" }
25///     fn as_any(&self) -> &dyn Any { self }
26/// }
27///
28/// // Trace is a no-op if the struct holds no GcPtr/Value fields.
29/// impl Trace for Counter {
30///     fn trace(&self, _: &mut MarkVisitor) {}
31/// }
32/// ```
33pub trait NativeObject: Send + Sync + fmt::Debug + Trace + 'static {
34    /// Short type name used for `type_tag_of` and protocol dispatch.
35    ///
36    /// Convention: use the Rust struct name (e.g. `"TcpStream"`, `"Counter"`).
37    /// This string is what you pass to `extend-type` on the Clojure side.
38    fn type_tag(&self) -> &str;
39
40    /// Downcast support — native functions that know the concrete type can
41    /// use `obj.as_any().downcast_ref::<MyType>()` to access it.
42    fn as_any(&self) -> &dyn Any;
43}
44
45/// A type-erased wrapper around `Box<dyn NativeObject>`.
46///
47/// This is what `GcPtr` actually points to inside `Value::NativeObject`.
48/// The wrapper exists because `GcPtr<dyn NativeObject>` would require
49/// unsized coercions; a concrete struct is simpler.
50#[derive(Debug)]
51pub struct NativeObjectBox {
52    inner: Box<dyn NativeObject>,
53}
54
55impl NativeObjectBox {
56    /// Wrap a concrete `NativeObject` implementor.
57    pub fn new(obj: impl NativeObject) -> Self {
58        Self {
59            inner: Box::new(obj),
60        }
61    }
62
63    /// The type tag for protocol dispatch.
64    pub fn type_tag(&self) -> &str {
65        self.inner.type_tag()
66    }
67
68    /// Downcast to a concrete type.
69    pub fn downcast_ref<T: NativeObject>(&self) -> Option<&T> {
70        self.inner.as_any().downcast_ref::<T>()
71    }
72
73    /// Access the inner trait object.
74    pub fn inner(&self) -> &dyn NativeObject {
75        &*self.inner
76    }
77}
78
79impl Trace for NativeObjectBox {
80    fn trace(&self, visitor: &mut MarkVisitor) {
81        self.inner.trace(visitor);
82    }
83}
84
85/// Convenience: allocate a `NativeObject` on the GC heap.
86pub fn gc_native_object(obj: impl NativeObject) -> GcPtr<NativeObjectBox> {
87    GcPtr::new(NativeObjectBox::new(obj))
88}