Skip to content

Pointer and Reference Types

Jiang separates ownership, borrow capability, raw-pointer capability, and binding mutability. Resource movement and lifetime rules are covered in Ownership, Borrowing & Lifetimes.

  • T^: an owning pointer.
  • T&: a shared readonly non-owning borrow.
  • T&!: a unique mutable non-owning borrow. It cannot be implicitly copied.
  • T*: a readonly raw pointer for FFI, ABI, and low-level code.
  • T*!: a writable raw pointer.
  • T[]&: a borrowed slice.
  • T[]^: an owning slice.
  • T[:S]&: a borrowed sentinel slice whose sentinel value is S.

Only T^ and T[]^ express language-level ownership. Compiler-owned names behind these forms are not available to user source; use the surface syntax directly.

Type-level ! is supported only after & or *: T&! and T*!. A ! after a binding name has a different purpose—it lets that variable, parameter, global, or field be reassigned and does not change its type.

Int value = 123;
Int& shared = value$.ref();
Int mutable_value! = 123;
Int&! unique = mutable_value$.mut_ref();
Int* raw = unsafe {
value$.ptr()
};
Int*! mutable_raw = unsafe {
mutable_value$.mut_ptr()
};

ref() and ptr() never manufacture write capability. The mutable forms are mut_ref() and mut_ptr(), and they require a writable source place. Raw-pointer creation, casts, and indexing remain inside unsafe.

T^ can auto-dereference when the expected type is T; $.get() requests an explicit pointee read:

Int^ make_value();
Int^ owner = make_value(); // preserve ownership
_ inferred = make_value(); // inferred: Int^
Int copied = make_value(); // expected Int: auto-dereference
Int explicit = owner$.get();

Member access can pass through an owning pointer:

struct Item {
Int value;
}
Item^ item = new Item(value: 42);
Int value = item.value;

References and raw pointers use $.get() for explicit reads. Writing requires a unique mutable borrow or mutable-pointee raw pointer:

Int value! = 0;
Int&! ref = value$.mut_ref();
ref$.set(41);
unsafe {
Int*! raw = value$.mut_ptr();
raw$.set(42);
}

T* and T*! can point into contiguous memory, but carry neither a length nor a sentinel guarantee. The old T[*] and T[*:S] types have been removed. Indexing is always unsafe:

unsafe {
UInt8* bytes = source$.ptr();
UInt8 first = bytes[0];
UInt8*! output = destination$.mut_ptr();
output[1] = 42;
}

Use T[]& when the API should carry a runtime length, and T[:S]& when it should carry a sentinel guarantee.

Void* and Void*! cannot be dereferenced or indexed because they have no element type. Cast to a concrete raw-pointer type before accessing memory.

$ prevents receiver auto-dereference and enters the implicit operation layer. Operations such as $.get(), $.set(), $.ref(), $.mut_ref(), $.ptr(), $.mut_ptr(), and $.move() are covered in Implicit Operation Layer.