Skip to main content

Shape

Enum Shape 

Source
pub enum Shape {
Show 14 variants Free, Word(Repr), Struct { fields: Vec<Field>, opaque: bool, }, Enum { cases: Vec<Case>, payload: Vec<Repr>, }, Str, Bytes, Elements { elem: LayoutId, growable: bool, }, Vector { elem: LayoutId, }, ByteBuffer, Members { elem: LayoutId, }, Entries { key: LayoutId, value: LayoutId, }, Closure { function: FunctionId, captures: Vec<LayoutId>, }, Shared { value: LayoutId, }, Boxed,
}
Expand description

How a family’s words are arranged.

Variants§

§

Free

A run of free words. Not a value; see LayoutId::FREE.

§

Word(Repr)

One word of the given interpretation.

The width-one case of the whole model, and the one every scalar is.

§

Struct

Consecutive fields, inline.

Fields

§fields: Vec<Field>
§opaque: bool

Whether the declaration was export opaque struct.

A fact about a declaration on a table that otherwise describes families, and it is here because nothing downstream can derive it: by the time a value is a word, the declaration is gone. What reads it is a rendering, which shows an opaque value’s name and nothing else — its fields are the declaring module’s business, and a rendering is read by whoever the string reaches.

§

Enum

Word 0 is the case index; the words after it are the payload region.

The region is wide enough for every case, and its per-word Reprs are in Shape::Enum::payload. Every case that uses a payload word agrees on that word’s Repr — the lowering assigns offsets under that constraint — because one static reference map has to be right whatever case a value holds. A word cannot be a reference in one case and an integer in another.

Two things follow. Constructing a case zeroes the payload words it does not fill, so a reference word belonging to another case reads null. And a collection never reads the discriminant: the region’s map is static, which is one fewer thing that can be wrong.

The cost is a region that can be wider than the widest case. That is the price of a static map, paid in words rather than in a run-time question.

Fields

§cases: Vec<Case>
§payload: Vec<Repr>

The payload region’s words, after the discriminant.

§

Str

UTF-8 bytes, eight to a word, little end first. The header’s len is the byte count, so the payload is len.div_ceil(8) words and the trailing bytes of the last word are zero.

§

Bytes

Packed bytes, eight to a word, little end first, exactly like Shape::Str’s payload — but not yet a String.

ADR 0051 gives lowering an internal construction run: an object crate::Inst::AllocBytes allocates, crate::Inst::WriteByte and crate::Inst::CopyBytes fill, and crate::Inst::FinishString turns into a String without copying. It is an IR/runtime value, not a Cove type — no declaration names it and no source expression produces one.

The header’s len is a byte count, the same as Shape::Str’s, which is what lets the runtime’s Machine::relabel turn a finished run into a String of the same header length without touching a payload word. Its payload holds no references — arbitrary written bytes are never a LayoutId or an address — so a run that is only half filled is exactly as safe for the collector to walk as a finished one: Layout::may_hold_refs answers false for it below, the same answer it gives Shape::Str.

This is deliberately not Shape::Str. ADR 0051 says “a run under construction is not a String”, and giving it a different shape is how that is enforced without a runtime tag check on every ordinary reference operation: is_string at crates/cove-runtime/src/vm/builtins.rs:564 matches on Shape::Str alone, so a Bytes run fails it and every place that asks “is this really a string” — the Host boundary, a call argument, a captured value — refuses it for the ordinary reason a Str-only match already refuses anything else, not because of a tag this shape adds.

§

Elements

The header’s len elements, each elem’s words, contiguous.

One shape covers Array<T> for every T, and is also what a Shape::Vector stores its elements in — growable says which of the two an object is.

Fields

§growable: bool
§

Vector

Payload word 0 is the element count; word 1 is a reference to the Shape::Elements object holding them.

The indirection is what a growable value needs and an immutable one does not. A Vector’s identity is observable — is is defined for it and mutation through one copy is visible through every other — so growing must not move the object a program is holding. The header stays where it is and the store beneath it is replaced by a larger one. An Array needs none of that and pays none of it.

Fields

§

ByteBuffer

Payload word 0 is the logical length in bytes; word 1 is a reference to a Shape::Bytes store whose own header length is its capacity.

ADR 0052’s stable owner, for bytes. The reason it is two objects rather than one is the reason Shape::Vector is: growth replaces the store, and the owner does not move, so every alias and every var address to it is still the same address afterwards. A run that grew by reallocating itself would leave a formatter’s var out parameter pointing at the object it used to be, which is exactly the failure the ADR’s “if the object itself moves when it grows, every alias and var address to it goes stale” names.

The split is also what keeps capacity out of the language. A store’s header length has to be its capacity, because the allocator and the collector walk whole physical objects; the logical length lives in the owner, so the spare room [length, capacity) is unobservable and exceeding an initial capacity grows rather than changing what a program answers.

This is the byte case of what Shape::Vector already is for word elements. The two differ in the storage unit and in the reference map and in nothing else: a byte run packs eight bytes to a word and holds no references, an element run stores values at the element layout’s stride and is traced by that layout. ADR 0052’s generic Buffer<E> will subsume both, and this is deliberately not generalised before the second case exists — the ADR’s own reason for doing bytes first is that a shared abstraction with one instance is a guess about the second.

Word 1 is always a reference, so Layout::may_hold_refs answers true and a collection traces word 1 and only word 1: word 0 is a length, and reading it as an address would chase an integer. The payload is a fixed two words whatever the store’s capacity, exactly as Shape::Vector’s is — Layout::fixed_payload_words answers 2 rather than None, which is what lets a static reader bound an access into an owner without a header to consult.

§

Members

The header’s len members, ascending and distinct.

Fields

§

Entries

The header’s len entries — key then value — ascending by key.

Fields

§value: LayoutId
§

Closure

Payload word 0 is the callee’s FunctionId; the words after it are the captures, each inline under its own layout.

Fields

§function: FunctionId
§captures: Vec<LayoutId>
§

Shared

Payload word 0 is the cell’s lock; the words after it are the wrapped value, inline under value’s own layout.

ADR 0008 makes Shared<T> the one handle that crosses a task boundary by sharing rather than by copying, and this is where that sharing is: an ordinary object in the run’s one heap, whose lock is one of its own words rather than an entry in a table keyed by address. So there is nothing to reclaim when a cell dies and no second lifetime running beside the collector’s — a cell is swept like anything else.

The value is inline for the reason a struct’s fields are: a value’s words are where the value is. What that buys here is that lock hands its closure the address of SHARED_VALUE — the ordinary var alias the language already describes — and nothing is copied in or out.

One layout per wrapped-value layout, interned the way Array<T> is. The lock word is an Int in the flattened map, so a collection traces nothing from it; the arrangement is Shape::Closure’s — one untraced word, then a value inline — which is why it needs no idea the collector did not already have.

Fields

§value: LayoutId
§

Boxed

Payload word 0 is a LayoutId; the words after it are a value of that layout, inline.

This is what an intentionally erased value occupies, and it is the only thing it is: dyn Trait, and a Host result a schema declared Any. Erasure is where a value stops having a static width, and a heap object is where a value without a static width lives.

A recursive layout used to share this shape, and ADR 0035 took that away: an implicitly recursive value type is a checker error, so erasure and recursion no longer share a mechanism and this has one meaning.

Trait Implementations§

Source§

impl Clone for Shape

Source§

fn clone(&self) -> Shape

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Shape

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for Shape

Source§

impl PartialEq for Shape

Source§

fn eq(&self, other: &Shape) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl StructuralPartialEq for Shape

Auto Trait Implementations§

§

impl Freeze for Shape

§

impl RefUnwindSafe for Shape

§

impl Send for Shape

§

impl Sync for Shape

§

impl Unpin for Shape

§

impl UnsafeUnpin for Shape

§

impl UnwindSafe for Shape

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.