shared<T> and weak<T>
shared<T> gives several handles joint ownership of one allocation. weak<T> observes the same control block without extending the value's lifetime and upgrades safely with the lock value expression.
Create shared ownership
shared<Resource> resource = new Resource(42);
shared<int> number = new int(42);
weak<int> observer = number;
shared<int[]> values = new int[100];
Direct initialization or assignment from a matching fresh new allocation adopts that allocation and creates one control block with exactly one strong owner. It does not leave a simultaneous raw or unique owner. Copying or moving an existing shared<T> preserves its control block instead of creating an independent one. A raw pointer or raw array variable cannot be converted to shared.
An object owner uses pointer-style access such as resource->Reset(). Primitive owners use *number; all built-in primitives except void are supported. A shared array supports indexing, Length, Rank and GetLength. The representation of ownership handles and their control block is internal and is not a native ABI contract.
Copy, move and assignment
shared<Resource> first = new Resource();
shared<Resource> copy = first; // retain: strong count + 1
shared<Resource> transferred = move first; // transfer: no retain
copy = copy; // safe self-assignment
copy = transferred; // retain new, release old
A shared copy duplicates only the handle and increments the strong count. A move transfers the same handle without allocating, retaining or releasing it; the source becomes moved and cannot be used until reinitialized. Assignment retains the incoming live handle before releasing the old destination, and self-assignment is safe.
shared<T> is copyable but each copy retains ownership. Copying a struct or concrete generic value recursively retains its shared fields; moving it transfers those fields without additional retains. Partial and conditional field moves use the normal moved-place rules, so only live fields are later released.
Copies and moves preserve the identity of the shared control block for borrow checking. Different handle variables are not assumed to name different objects:
shared<Resource> a = new Resource();
shared<Resource> b = a;
Resource& first = *a;
Resource& second = *b; // error while first remains active
Safe references borrow the shared pointee rather than an individual handle. One mutable borrow excludes all overlapping access, multiple readonly borrows may coexist, and known-disjoint field projections remain independent. When control flow or a callable erases the exact control-block origin, shared identity is merged conservatively instead of assuming that possibly aliasing handles are distinct.
Strong release and final destruction
Destroying a live shared handle decrements the strong count. While another strong owner exists, the allocation stays alive. The transition to zero runs the complete destructor behavior of T exactly once and deallocates the object or array through Xenon's allocator ABI.
For shared<T[]>, elements that require destruction are destroyed in reverse order before array storage is freed. The control block remains alive until all weak observers also disappear. An empty shared handle performs no release.
Creating, copying, moving, overwriting, returning, or acquiring shared<T> requires the complete destructor of T to be accessible at that operation. The recursive check covers array elements, base structs, nested fields, and nested ownership. weak<T> performs no direct pointee-destruction check because destroying a weak handle never destroys T.
GetShared()->Use();
// the returned shared temporary is released after Use returns
A returned shared owner used inside a larger expression lives until the end of that full expression. It therefore keeps the pointee alive throughout member access and is released without leaking an extra strong count.
Observe with weak
shared<Resource> strong = new Resource();
weak<Resource> observer = strong;
weak<Resource> another = observer; // weak retain only
weak<Resource> transferred = move another; // no retain
A weak handle can be created only from a matching live shared owner or copied from another weak handle. It does not increase the strong count and never directly destroys T. Its destructor releases one weak observer and may free the already-expired control block.
Direct value access through weak<T> is rejected. Upgrade first:
shared<Resource> locked = lock observer;
if (locked != null)
{
locked->Use();
}
If the allocation is alive, lock observer checks the strong count, adds one strong owner and returns it. If the allocation has expired, it returns an empty shared<T>. The empty handle is a valid live value; it is not the compiler's moved state. A shared handle is not implicitly a Boolean; test it using the language's supported explicit comparison, as above.
A successful lock preserves the observed control-block identity. Consequently a reference through the locked handle conflicts with an overlapping mutable reference through any existing shared owner of that same allocation.
bool alive = (lock observer) != null;
// the temporary strong owner is released after the comparison
lock is a value-producing ownership expression, so its result must be consumed by an initializer, assignment, return, function argument, or larger expression such as the explicit null comparison above. A standalone lock observer; is rejected instead of immediately acquiring and discarding a strong owner.
Native-thread concurrency
Different shared<T> handles that refer to the same allocation may be copied, moved and destroyed concurrently by native threads. Strong and weak ownership counters are updated atomically, exactly one final strong release destroys the value, and the control block remains alive until its last weak observer is released.
Likewise, different weak<T> handles may be copied and destroyed concurrently. lock observer performs an atomic conditional upgrade: it either establishes a new strong owner while the value is still alive or returns an empty shared handle. Once the strong count reaches zero, a later lock cannot resurrect the value.
shared<Resource> first = new Resource();
shared<Resource> second = first;
// A native host may operate on first and second from different threads.
This guarantee protects ownership lifetime management, not the contents of T. Unsynchronized concurrent writes through first and second are still a data race. Moving one handle transfers it as before; concurrently accessing the same handle storage while another thread moves from or overwrites that storage is not made safe by reference counting. The complete rule is specified by the memory model.
Empty shared handles
shared<Resource> empty = null;
shared<Resource> second = move empty;
// empty is moved; second is a valid empty handle
null constructs an empty shared handle and compares equal to one. Moving an empty handle still moves the source at the language level. This distinction keeps flow analysis independent of the runtime pointer bits.
Fields, generics and templates
struct Box<T> { public T Value; }
Box<shared<Resource>> owner;
Box<weak<Resource>> observer;
void Consume<T>(shared<T> value) { }
Consume(owner.Value); // infers T = Resource
Generic inference and structural template matching recurse through shared and weak, including template-self signatures such as shared<TemplateSelf>. Ownership kind is part of type identity: unique<T>, shared<T> and weak<T> never match one another accidentally.
A concrete generic struct recomputes copyability and destruction requirements from its substituted fields. Box<shared<T>> and Box<weak<T>> are copyable with non-trivial copy and destructor support; Box<unique<T>> is non-copyable. Direct ownership-wrapper nesting is rejected, while ownership wrappers inside ordinary containers and arrays remain supported.
Cycles
Strong reference cycles are allowed to leak, as with ordinary reference counting. Use weak fields for back-references that must not keep a cycle alive; Xenon does not run a tracing garbage collector.
Manual release and C ABI
free and delete reject shared or weak handles; automatic destruction is the only ownership release path. Direct extern and export signatures containing ownership wrappers are also rejected because their representation and control-block ABI are internal. Use raw pointers plus an explicit native lifetime contract at C boundaries.