In Starlark, there are three interesting heap-related points of interest:
- A
Heap
hasValue
's allocated on it and cannot be cloned or shared. - A
FrozenHeap
hasFrozenValue
's allocated on it and cannot be cloned or shared. - A
FrozenHeapRef
is aFrozenHeap
that is now read-only and can now be cloned and shared.
A FrozenHeapRef
keeps a heap alive. While you have a FrozenValue
, it is important that you have either the FrozenHeap
itself, or more usually, a FrozenHeapRef
to it. A FrozenHeap
may contains a set of FrozenHeapRef
's to keep the FrozenHeap
s it references alive.
Heaps are included in other data types:
- A
Module
contains aHeap
(where normal values are allocated) and aFrozenHeap
(stores references to other frozen heaps and has compilation constants allocated on it). TheHeap
portion is garbage collected. At the end, when you callfreeze
,Value
's referenced by name in theModule
are moved to theFrozenHeap
and then thenFrozenHeap
is sealed to produce aFrozenHeapRef
. - A
FrozenModule
contains aFrozenHeapRef
. - A
GlobalsBuilder
contains aFrozenHeap
onto which values are allocated. - A
Globals
contains aFrozenHeapRef
.
It is important that when a FrozenValue
X is referenced by a Value
or FrozenValue
(for example, included in a list), the heap where X originates is added as a reference to the heap where the new value is being created.
As a concrete example in pseudo-code:
let h1 = FrozenHeap::new();
let s = "test".alloc(h1);
let h1 : FrozenHeapRef = h1.into_ref();
let h2 = Heap::new();
h2.add_reference(h1);
vec![s].alloc(h2);
In the above code, the following steps are taken:
- Create a
FrozenHeap
then allocate something in it. - Turn the heap into a reference.
- Use the allocated value
s
fromh1
when constructing a value inh2
. - For that to be legal, and for the heap
h1
to not disappear while it is being allocated, it is important to calladd_reference
.
Note that this API can only point at a FrozenValue
from another heap, and only after that heap has been turned into a reference, so it will not be allocated in anymore. These restrictions are deliberate and mean that most programs only have one 'active heap' at a time.
Following are some places where heap references are added by Starlark:
- Before evaluation is started, a reference is added to the
Globals
from theModule
, so it can access the global functions. - When evaluating a
load
statement, a reference is added to theFrozenModule
that is being loaded. - When freezing a module, the
FrozenHeap
, in theModule
, is moved to theFrozenModule
, preserving the references that were added.
When you get a value from a FrozenModule
, it will be a OwnedFrozenValue
. This structure is a pair of a FrozenHeapRef
and a FrozenValue
, where the ref keeps the value alive. You can move that OwnedFrozenValue
into the value of a module with code such as:
fn move<'v>(from: &FrozenModule, to: &'v Module) {
let x : OwnedFrozenValue = from.get("value").unwrap();
let v : Value<'v> = x.owned_value(&to);
to.set("value", v);
}
In general, you can use the OwnedFrozenValue
in one of three ways:
- Operate on it directly - with methods like
unpack_i32
orto_str
. - Extract it safely - using methods like
owned_frozen_value
, which takes aFrozenHeap
to which the heap reference is added and returns a nakedFrozenValue
. After that, it is then safe for theFrozenHeap
you passed in to use theFrozenValue
.- With
owned_value
, there is lifetime checking that the right heap is passed, but withFrozenValue
, there isn't. - Be careful to pass the right heap, although given most programs only have one active heap at a time, it should mostly work out.
- With
- Extract it unsafely - using methods
unchecked_frozen_value
, which gives you the underlyingFrozenValue
without adding any references.- Be careful to make sure there is a good reason the
FrozenValue
remains valid.
- Be careful to make sure there is a good reason the