References

A reference aliases an existing value while preserving typed member access and overload resolution.

Binding a reference

Add & to a type to declare a reference. A reference must be initialized from a compatible value and does not create a copy.

int value = 10;
int& alias = value;

alias = 42; // value is now 42

References use normal value syntax: member access is written with ., and a reference is passed to a T& parameter without an explicit address-of operator.

Mutable and readonly references

T& is an exclusive mutable reference. readonly T& is a shared immutable reference. The readonly qualifier is explicit: Xenon never reinterprets an unqualified T& as readonly based on how it happens to be used.

void Increment(int& value)
{
    value += 1;
}

int Read(readonly int& value)
{
    return value;
}

While an exclusive mutable reference is active, the overlapping place is accessed through that reference. Direct owner access, another mutable reference, and a readonly reference all conflict. Multiple readonly references may coexist; the owner may still read, but neither the owner nor those references may mutate the overlapping place.

Foo value = Foo();
Foo& reference = value;
reference.X = 10; // valid
reference.Update();       // valid

// Invalid while reference remains active:
// value.X = 20;
// readonly Foo& reader = value;
// Foo& second = value;
Foo value = Foo();
readonly Foo& a = value;
readonly Foo& b = value;
a.GetX();
b.GetY();
value.GetZ(); // readonly owner access remains valid

A mutable reference may convert to a readonly reference only as a safe reborrow. The reverse conversion is not allowed because it would restore write access.

The rule is based on the referenced semantic place rather than the spelling of the expression. It is the same for an ordinary value, the live value in storage<T>, a unique<T> or shared<T> pointee, and a pointee reached through a known raw-pointer root. One active T& excludes every overlapping mutable or readonly borrow; any number of readonly T& borrows may coexist.

Flow-sensitive borrow lifetimes

A borrow normally ends after its final use, not mechanically at the closing brace. This permits the owner to become usable again without an artificial nested scope:

Foo value = Foo();
Foo& reference = value;
reference.X = 10; // final use of reference
value.X = 20;     // valid

Borrow conflicts are computed from semantic places. A borrow of object.first blocks relocation of object, but it does not block independent access to object.second. Two mutable references to provably disjoint fields may coexist.

Every operation that transfers or ends the current object lifetime respects active borrows. An active reference therefore prevents both move and destruct on its place or any live ancestor place. Once the reference has no future use, either operation becomes valid.

Foo& reference = value;
reference.Update();
destruct(value); // valid after the final reference use
Foo& reference = value;
destruct(value); // error: reference remains active
reference.Update();

The same overlap rule applies recursively: a reference to object.Field blocks destruction of object, while a reference rooted in an unrelated object does not.

Pointee identity preserves known aliases and field projections. Copies of a shared handle, lock weakValue, ownership moves, and known raw-pointer copies therefore continue to name the same borrow root. Two fields such as owner->First and owner->Second remain disjoint, while the whole pointee overlaps either field. Runtime pointer indices into the same known allocation are treated conservatively when disjointness cannot be proven.

Lifetime authority is distinct from value access. A T& borrowed from storage<T> accesses only its current live value and cannot use destruct(reference) or move reference, because neither form can update the storage's Empty/Initialized state. A mutable storage<T>& references the storage object itself and may initialize, destroy, move from and reuse that storage. A readonly storage<T>& cannot change this state.

Compile-time only

References remain lightweight native addresses. Borrow checking adds no runtime tables, counters, tracing or garbage collection.

Raw-pointer boundary

Xenon tracks safe references derived from known raw-pointer roots and preserves identity across known pointer copies. Raw pointers remain an unmanaged escape hatch: arbitrary aliases returned by unrelated native or pointer-producing operations are not turned into runtime alias metadata or a runtime borrow system.

Callable references and lifetime authority

A reference provides access to a value. It provides lifetime-management authority only when Xenon can resolve exactly one authoritative owner and update that owner's move, destruction, or storage state. If provenance is unknown or has several possible origins, move and destruct are rejected rather than guessing.

Resource value = Resource();
Resource& reference = value;
destruct(reference); // valid: reference → value, and value becomes destructed

An ordinary callable parameter of type T& grants exclusive mutable access, but it does not identify the caller's authoritative lifetime owner. The callee may update fields and call mutating methods; it may not move or destruct the referenced value or a lifetime-rooted child. This remains true whether the caller supplied an ordinary value, initialized storage, or another owner.

void Modify(Resource& value)
{
    value.Update();       // valid mutation
}

void Destroy(Resource& value)
{
    destruct(value);      // error: T& has no lifetime authority
}

If an API intentionally manages explicit storage lifetime, declare storage<T>&. The reference then points to the storage wrapper and its persistent Empty/Initialized state, so destruction, reconstruction and whole-value moves update the authoritative owner.

void Destroy(storage<Resource>& value)
{
    destruct(value);      // valid
}

That authority belongs only to the complete storage wrapper. Projecting its contained T, a field, or a nested field produces an access-only reference even when the projection is returned through one or more functions. Such a reference cannot make the wrapper's single Empty/Initialized state represent a partial lifetime.

Resource& GetValue(storage<Resource>& value) { return value; }

storage<Resource> slot = Resource();
storage<Resource>& storageRef = slot;
Resource& valueRef = GetValue(slot);

destruct(storageRef); // valid: the complete storage becomes Empty
// destruct(valueRef); // invalid: access to T is not storage authority

Returning or forwarding a reference does not manufacture authority. Provenance is followed through ordinary and generic functions and reference-returning methods. A single return path such as Forward(local) can resolve back to that local; forwarding a T& parameter remains access-only, and a conditional return with several possible owners cannot be a lifetime-operation target.

Resource& Forward(Resource& value) { return value; }

Resource local = Resource();
destruct(Forward(local)); // valid: Forward(local) → local

void Invalid(Resource& parameter)
{
    destruct(Forward(parameter)); // error: still caller-owned
}

References stored in values

Putting a reference in a struct does not weaken its borrowing rules. A Data& field carries an active exclusive mutable borrow, while a readonly Data& field carries a shared immutable borrow. Moving the referenced owner remains invalid while the carrying value can still use that reference.

struct View { public Data& Data; }
struct ReadView { public readonly Data& Data; }

Data data = Data();
View view = View { data };
int observed = view.Data.Value; // final use: the borrow may end here
Data moved = move data;

A reference field is always a borrow of an external lifetime, never a lifetime owner of the containing value. Consequently destruct(view.Data) and move view.Data are invalid, including through nested reference fields or a method that returns the field by reference.

The borrow lifetime follows the aggregate carrying it and remains flow-sensitive. Whole-value assignment replaces its reference provenance, move transfers it to the destination, and copying a value containing a mutable reference is rejected because it would duplicate an exclusive reference. Constructors summarize the actual bound assignments to each reference field, including reversed parameter mappings and alternative origins from conditional branches; field order and parameter order are never used as a guess. Initialized storage<T> carries the same reference metadata as a normal T; destruct(storage) ends those borrows and initializing the storage again installs only the new value's provenance.

Compile-time metadata

Reference provenance is compiler state only. Struct and storage layouts do not gain runtime borrow metadata.

Struct and interface references

References preserve polymorphic conversions. A derived struct reference may bind to a base struct reference, and a struct reference may bind to an interface reference implemented by that struct.

Sprite sprite = Sprite(true);
IRenderable& renderable = sprite;

renderable.Render();

Calls through an interface reference use the concrete struct's implementation. Readonly receivers may call members whose contract is declared readonly.

Returning references

Functions and methods may return a reference only when the referenced storage outlives the call. Forwarding a reference parameter is valid because its storage belongs to the caller:

int& Forward(int& value)
{
    return value;
}

A field reached through a reference parameter is caller-owned as well. A method may return a field of its receiver because this is not local storage created by that method:

struct Container
{
    private int value;

    public int& Get()
    {
        return value;
    }
}

Reference-return provenance is composed through ordinary calls. A forwarding function records whether its result originates from a particular reference parameter, the receiver, a projected field, or static storage. That summary is followed transitively through multiple forwarding layers and concrete generic specializations.

int& Forward(int& value) { return value; }

int& InvalidForward()
{
    int local = 10;
    return Forward(local); // error: forwarding does not extend local lifetime
}

Returning a reference to a local value, a field rooted in a local value, a by-value parameter, or a temporary is invalid:

int& Invalid()
{
    int value = 10;
    return value; // error: Invalid's stack frame owns value
}

readonly T& follows the same lifetime rule: readonly access prevents mutation but does not extend storage lifetime. Extern, interface, virtual, and raw-pointer-derived reference returns whose origin is not proven are rejected conservatively. Raw pointer values themselves retain their separate low-level lifetime contract.

Reference lifetime safety

Xenon references may point to stack values, but a returned reference may not outlive the storage it refers to. A reference does not own storage and is not released with free; the referenced value must remain alive for every use of the reference.