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}