pub struct Cell(/* private fields */);
Expand description
A reference-counting pointer to heap-allocated memory managed by the Script Engine.
The Cell object provides the foundational mechanism for modeling Script memory and ensuring interoperability between Script data and Rust data.
§Cell Design
A unit of Script memory allocation is an array of elements, all of the same type known to the Engine. Typically, this is an array with just one element.
The Cell represents a pointer to this memory allocation. This pointer can either reference memory owned by the Engine, a projection of the memory allocation, or a reference to a portion of the allocation (not necessarily owned by the Engine).
Each Cell is a reference-counting pointer. Cloning a Cell increases the reference count, while dropping an instance decreases it. When the last instance of a Cell pointing to memory owned by the Script Engine is dropped, the Engine deallocates the memory.
The Engine tracks the borrowing status of pointers. For example, if you obtain an immutable reference from a Cell’s pointer, you cannot acquire a mutable reference from another clone of this Cell until the immutable reference is released.
Additionally, the Engine ensures that dereferencing a Cell pointer that is a projection of another Cell pointer adheres to Rust’s borrowing rules. For example, if one Cell refers to a chunk of memory of another Cell, immutable dereferencing of either Cell and mutable dereferencing of the other are prohibited by the Script Engine.
These borrowing rules are compatible with Rust’s borrowing rules and the Miri machine.
§Creation
You can create a Cell object using one of the memory-transfer functions that hand control over the Rust object (or an array of objects) to the Script Engine:
-
Cell::give: Creates a Cell from the specified object. Typically, this function creates a Cell that points to a singleton array containing the object you provided. However, depending on the type’s upcasting procedure, the Engine may create an array of multiple elements instead. For example, the Script Engine interprets String as an array of bytes.
-
Cell::give_vec: Explicitly creates an array of objects from the specified vector.
-
Cell::nil: Creates a special kind of Cell that intentionally does not reference any memory allocation. Most of the Cell access functions will yield RuntimeError::Nil if you attempt to access the underlying data. Additionally, Nil is the default value of the Cell object.
let cell = Cell::give(Origin::nil(), 125u16).unwrap();
assert_eq!(cell.length(), 1);
assert!(cell.is::<u16>());
assert!(!cell.is_nil());
let cell = Cell::give_vec(Origin::nil(), vec![10u8, 20, 30]).unwrap();
assert_eq!(cell.length(), 3);
§Cell Type Inspection
You can obtain runtime metadata about the underlying data type using the Cell::ty function, and determine the length of the array using the Cell::length function.
Within the type metadata, you can further inspect the operations available for this type using the TypeMeta::prototype function.
§Data Access
Operations you can perform on the Cell’s underlying data can be divided into higher-level and lower-level memory operations.
Higher-level operations involve working with the data as a type exported from Rust and in accordance with the Prototype of this type.
Lower-level operations involve using the Cell’s API functions directly, such as data borrowing and data projections.
Most operations (both high- and low-level) either consume the Cell or render the instance obsolete. Therefore, if you need to perform more than one operation, you should Clone the Cell and use the cloned instances for each operation separately.
§Object Operations
To work with a Cell as an object of the exported Rust type, you should convert the Cell into an [Object] using the Cell::into_object function.
let lhs = Cell::give(Origin::nil(), 10usize).unwrap();
let rhs = Cell::give(Origin::nil(), 20usize).unwrap();
let result = lhs
.into_object()
.add(Origin::nil(), Origin::nil(), Arg::new(Origin::nil(), rhs))
.unwrap();
let result_value = result.take::<usize>(Origin::nil()).unwrap();
assert_eq!(result_value, 30);
§Taking Data Back
You can retrieve the data from a Cell using one of the Cell::take, Cell::take_vec, or Cell::take_string functions.
If the Cell
is the last instance pointing to the data owned by the Script
Engine, the function will return the data without cloning. If there are
additional clones of the Cell, or if the data is a projection of another
memory allocation, the Cell will attempt to clone the data. If the type
does not support cloning, you will receive a
RuntimeError::UndefinedOperator error.
let cell = Cell::give(Origin::nil(), 125u16).unwrap();
let cell2 = cell.clone();
assert!(cell.take::<f32>(Origin::nil()).is_err());
assert_eq!(cell2.take::<u16>(Origin::nil()).unwrap(), 125);
§Data Borrowing
The Cell::borrow_ref, Cell::borrow_mut, Cell::borrow_slice_ref, Cell::borrow_slice_mut, and Cell::borrow_str functions provide a way to obtain normal Rust references to the underlying data without cloning or transferring ownership.
These functions return Rust references with the same lifetime as the Cell instance, ensuring that the reference cannot outlive the Cell’s instance.
When borrowing data, the current instance of the Cell becomes obsolete. You cannot use it anymore, and you should drop this Cell after dropping the reference.
let mut cell1 = Cell::give(Origin::nil(), 125u16).unwrap();
let mut cell2 = cell1.clone();
let mut cell3 = cell1.clone();
assert_eq!(cell1.borrow_ref::<u16>(Origin::nil()).unwrap(), &125);
// Cannot mutably borrow the same data because an immutable reference is
// still active.
assert!(cell2.borrow_mut::<u16>(Origin::nil()).is_err());
// Dropping the immutable reference.
drop(cell1);
assert_eq!(cell3.borrow_mut::<u16>(Origin::nil()).unwrap(), &mut 125);
§Data Projections
You can map a Cell’s data to another Cell that refers to the memory allocation related to the original data.
For example, using Cell::map_ptr, you can project a pointer to a Rust structure to one of its fields without dereferencing the struct pointer. Alternatively, with Cell::map_ref, you can map a reference to the underlying object to a reference of another object with the same lifetime.
let cell = Cell::give_vec(Origin::nil(), vec![10u16, 20, 30, 40]).unwrap();
let mut mapped_cell = cell.map_slice(Origin::nil(), 2..).unwrap();
assert_eq!(
mapped_cell.borrow_slice_ref::<u16>(Origin::nil()).unwrap(),
&[30, 40],
);
§Strings
The Ad Astra Engine has a special case for string data ([str]
type).
The Cell stores strings as arrays of bytes ([u8]
), but it also sets a
flag indicating that this array is safe for UTF-8 decoding.
The Cell API includes special functions (e.g., Cell::borrow_str) that interpret arrays of bytes as Unicode strings.
§Zero-Sized Types
Zero-Sized Types (ZSTs) have special handling in Ad Astra. If you create an array of ZST elements, the Cell stores the length of the array but allows addressing of elements outside of the array bounds without errors.
Additionally, data access operations on ZST Cells are exempt from some of the borrowing rules. For example, you can obtain mutable and immutable references to the same zero-sized data without conflicts.
One exception is the ()
unit type. When you give a unit
value ()
to the function, it returns a Cell::nil value, with the Nil
Cell representing the Rust unit value.
Implementations§
Source§impl Cell
impl Cell
Sourcepub fn stringify(&self, alt: bool) -> String
pub fn stringify(&self, alt: bool) -> String
A convenient utility function that formats Cell’s data for debugging purposes.
If the underlying type of the Cell implements display operations, the function uses the Display implementation to format the data. Otherwise, if the type implements debug operations, the function falls back to the Debug implementation.
This function temporarily borrows the underlying data. If the borrowing fails, or if the data type does not implement either Display or Debug traits, the result will be the signature of the type.
The alt
parameter specifies whether the function should use alternate
formatting (such as format!("{:#}")
) or not.
Source§impl Cell
impl Cell
Sourcepub const fn nil() -> Self
pub const fn nil() -> Self
Returns an instance of Cell that intentionally does not reference any memory allocation.
Most functions in the Cell API will return a RuntimeError::Nil error when called on this instance.
Sourcepub fn give(origin: Origin, data: impl Upcast<'static>) -> RuntimeResult<Self>
pub fn give(origin: Origin, data: impl Upcast<'static>) -> RuntimeResult<Self>
Gives ownership of the data to the Script Engine and returns a Cell that points to this data.
The origin
parameter specifies the Rust or Script source code range
where this object was created.
The data
parameter is the data of the created Cell.
This argument does not need to be of a type known to the Script Engine;
it can be any Rust type that can be upcasted to a known type.
For example, the generic Option type is not known to the
Script Engine. If the Option is Some, the upcasting procedure unwraps
the value; otherwise, it returns a unit ()
value (which is interpreted
as Cell::nil).
The function returns a RuntimeError
if the upcasting procedure fails
to upcast the data
object.
Sourcepub fn give_vec<T: ScriptType>(
origin: Origin,
data: Vec<T>,
) -> RuntimeResult<Self>
pub fn give_vec<T: ScriptType>( origin: Origin, data: Vec<T>, ) -> RuntimeResult<Self>
A lower-level alternative to the Cell::give function. Unlike the give function, give_vec does not perform upcasting of the data object. Instead, it transfers ownership of the vector’s array to the Script Engine as-is and returns a Cell that points to the vector’s memory allocation.
The generic parameter T
is the type of the elements in the array and
must be a type known to the Script Engine.
Sourcepub fn origin(&self) -> Origin
pub fn origin(&self) -> Origin
Returns the Rust or Script source code range indicating where the Cell’s data was created.
Sourcepub fn ty(&self) -> &'static TypeMeta
pub fn ty(&self) -> &'static TypeMeta
Returns runtime metadata for the Rust type of the underlying Cell’s data object (or the type of the elements in the Cell’s array).
If the Cell points to a [u8]
array of bytes, and these bytes encode a
Unicode string, the function returns metadata for the str type.
If the Cell is a Nil cell, the function returns metadata for the unit type.
Sourcepub fn is<T: ScriptType + ?Sized>(&self) -> bool
pub fn is<T: ScriptType + ?Sized>(&self) -> bool
Sourcepub fn length(&self) -> usize
pub fn length(&self) -> usize
Returns the number of elements in the array to which this Cell points.
Typically, the Cell object stores arrays with a single element, and the function returns 1.
Note that the Script Engine stores Rust strings as arrays of bytes. In this case, the function returns the number of bytes encoding the string.
If the Cell is a Nil Cell, the function returns 0.
Sourcepub fn take<T: ScriptType>(self, origin: Origin) -> RuntimeResult<T>
pub fn take<T: ScriptType>(self, origin: Origin) -> RuntimeResult<T>
Retrieves the data to which this Cell points.
If there are no other clones of this Cell, the function takes the value from the Script Engine and returns it as-is. Otherwise, the function attempts to clone the underlying data.
The origin
parameter specifies the Rust or Script source code range
from where the Cell’s data was accessed.
The function returns a RuntimeError if:
- The data requires cloning but cannot be immutably borrowed (e.g., the data is already borrowed mutably), or the type does not implement the cloning operator.
- The generic parameter
T
is not a type of the Cell’s data, or if the Cell points to an array with zero non-zero-sized (ZST) elements.
If the Cell
points to an array with more than one element, the
function returns the first element in the array.
For strings, this function can retrieve the first byte of the UTF-8
encoding (by specifying u8 as T
), but it is recommended to use the
take_string function instead, which returns the
entire string data.
Sourcepub fn take_vec<T: ScriptType>(self, origin: Origin) -> RuntimeResult<Vec<T>>
pub fn take_vec<T: ScriptType>(self, origin: Origin) -> RuntimeResult<Vec<T>>
Similar to the take function, but returns all elements of the underlying Cell’s array as a vector, even if the array has zero elements.
Sourcepub fn take_string(self, origin: Origin) -> RuntimeResult<String>
pub fn take_string(self, origin: Origin) -> RuntimeResult<String>
Similar to the take_vec function, but attempts to decode the underlying Cell’s array into a String.
If the type of the data is neither u8 nor str, the function returns an error.
If the type is u8 but it is unknown whether the underlying array of bytes is a UTF-8 encoding of a string, the function will attempt to decode the array. If decoding fails, the function returns an error too.
Sourcepub fn borrow_ref<T: ScriptType>(&mut self, origin: Origin) -> RuntimeResult<&T>
pub fn borrow_ref<T: ScriptType>(&mut self, origin: Origin) -> RuntimeResult<&T>
Returns an immutable reference to the Cell’s data.
The origin
parameter specifies the Rust or Script source code range
from where the Cell’s data was accessed.
The function dereferences the first element of the array to which this Cell points. If the array consists of more than one element or zero elements (which can be checked via the Cell::length function), the function returns a RuntimeError.
The function also returns an error if the Script Engine cannot grant
immutable access (e.g., if the data is already borrowed mutably), or if
the generic parameter T
is not a type of the Cell’s data.
The returned reference has the same lifetime as the Cell’s instance. The borrow_ref function turns the Cell’s instance into an invalid state, so you cannot use this instance again. You should drop the Cell after releasing the reference. If you need to use the Cell’s data after releasing the reference, you should clone the Cell before calling borrow_ref.
Sourcepub fn borrow_slice_ref<T: ScriptType>(
&mut self,
origin: Origin,
) -> RuntimeResult<&[T]>
pub fn borrow_slice_ref<T: ScriptType>( &mut self, origin: Origin, ) -> RuntimeResult<&[T]>
Similar to borrow_ref, but returns a reference to the entire array to which this Cell points, regardless of the array’s length.
Sourcepub fn borrow_str(&mut self, origin: Origin) -> RuntimeResult<&str>
pub fn borrow_str(&mut self, origin: Origin) -> RuntimeResult<&str>
Similar to borrow_slice_ref, but attempts to decode the underlying Cell’s array into a str.
If the type of the data is neither u8 nor str, the function returns an error.
If the type is u8 but it is unknown whether the underlying array of bytes is a UTF-8 encoding of a string, the function will attempt to decode the array. If decoding fails, the function returns an error.
Sourcepub fn borrow_mut<T: ScriptType>(
&mut self,
origin: Origin,
) -> RuntimeResult<&mut T>
pub fn borrow_mut<T: ScriptType>( &mut self, origin: Origin, ) -> RuntimeResult<&mut T>
Returns a mutable reference to the Cell’s data.
The origin
parameter specifies the Rust or Script source code range
from where the Cell’s data was accessed.
The function dereferences the first element of the array to which this Cell points. If the array consists of more than one element or zero elements (which can be checked via the Cell::length function), the function returns a RuntimeError.
The function also returns an error if the Script Engine cannot grant
mutable access (e.g., if the data is already borrowed), or if the
generic parameter T
is not a type of the Cell’s data.
The returned reference has the same lifetime as the Cell’s instance. The borrow_mut function turns the Cell instance into an invalid state, so you cannot use this instance again. You should drop the Cell after releasing the reference. If you need to use the Cell’s data after releasing the reference, you should clone the Cell before calling borrow_mut.
Sourcepub fn borrow_slice_mut<'a, T: ScriptType>(
&'a mut self,
origin: Origin,
) -> RuntimeResult<&'a mut [T]>
pub fn borrow_slice_mut<'a, T: ScriptType>( &'a mut self, origin: Origin, ) -> RuntimeResult<&'a mut [T]>
Similar to borrow_mut, but returns a mutable reference to the entire array to which this Cell points, regardless of the array’s length.
Sourcepub fn map_str(self, origin: Origin) -> RuntimeResult<Self>
pub fn map_str(self, origin: Origin) -> RuntimeResult<Self>
Creates a projection of the Cell’s data into another Cell that is guaranteed to point to a valid UTF-8 encoding of a string.
The origin
parameter specifies the Rust or Script source code range
from where the Cell’s data has been mapped.
The function’s behavior is similar to calling
borrow_str and then storing the resulting &str
reference in the returned Cell.
The map_str function is subject to the same requirements as the
borrow_str function.
This function immediately puts the data of the original Cell into an immutable borrow state. This borrowing will not be released until all clones of the projected Cell instance are dropped.
Sourcepub fn map_ref<From>(
self,
origin: Origin,
map: impl for<'a> MapRef<'a, From>,
) -> RuntimeResult<Self>where
From: ScriptType,
pub fn map_ref<From>(
self,
origin: Origin,
map: impl for<'a> MapRef<'a, From>,
) -> RuntimeResult<Self>where
From: ScriptType,
Creates a projection of the immutable reference to the Cell’s data into another reference, returning a Cell that points to the projected reference.
This function is useful when you want to borrow the Cell’s data, then call a function that returns another reference with the same lifetime, and store this reference in a new Cell.
The origin
parameter specifies the Rust or Script source code range
from where the Cell’s data has been mapped.
The map
parameter is a functor that takes a reference to the original
Cell’s data and returns data that can be upcasted into an
object with the same lifetime as the original Cell’s data reference. You
can use a normal Rust function as the map
argument if it satisfies
these requirements.
#[export]
pub struct Container {
value: usize,
}
fn map(container: &Container) -> RuntimeResult<&usize> {
Ok(&container.value)
}
let container = Cell::give(Origin::nil(), Container { value: 100 }).unwrap();
let projection = container.map_ref(Origin::nil(), map).unwrap();
assert_eq!(projection.take::<usize>(Origin::nil()).unwrap(), 100);
In practice, you might use an FnOnce closure as a functor. However, this can lead to limitations in Rust’s type system, where the compiler may not infer lifetime bounds correctly. To work around this, you can use a “funnel” pattern with an additional helper function:
fn funnel<F: FnOnce(&Container) -> RuntimeResult<&usize>>(f: F) -> F {
f
}
let projection = container
.map_ref(
Origin::nil(),
funnel(|container: &Container| Ok(&container.value)),
)
.unwrap();
The map_ref function borrows the original Cell data immutably and does
not release this borrowing until all clones of the projected Cell are
dropped. An exception occurs if the map
function returns owned data
(data with the 'static
lifetime). In this case, the original data
borrowing is not bound by the lifetime of the projection.
Consequently, the function may return a RuntimeError if the Script
Engine cannot grant immutable access to the original data (e.g., if the
data is already borrowed mutably). The function also returns an error if
the From
generic type is not a type of the original
Cell’s data, if the map
functor returns an error, or if the original
Cell’s data is an array of zero or more than one element (which can be
checked via the Cell::length function).
Sourcepub fn map_mut<From>(
self,
origin: Origin,
map: impl for<'a> MapMut<'a, From>,
) -> RuntimeResult<Self>where
From: ScriptType,
pub fn map_mut<From>(
self,
origin: Origin,
map: impl for<'a> MapMut<'a, From>,
) -> RuntimeResult<Self>where
From: ScriptType,
Similar to map_ref, but maps a mutable reference to the Cell’s data.
Unlike map_ref, the map_mut function borrows the original Cell’s data mutably and is subject to Rust’s general exclusive dereferencing rules.
Sourcepub fn map_ptr<From, To>(
self,
origin: Origin,
by_ref: Option<unsafe fn(from: *const From) -> *const To>,
by_mut: Option<unsafe fn(from: *mut From) -> *mut To>,
) -> RuntimeResult<Self>where
From: ScriptType,
To: ScriptType,
pub fn map_ptr<From, To>(
self,
origin: Origin,
by_ref: Option<unsafe fn(from: *const From) -> *const To>,
by_mut: Option<unsafe fn(from: *mut From) -> *mut To>,
) -> RuntimeResult<Self>where
From: ScriptType,
To: ScriptType,
Creates a projection of the raw pointer of the Cell into another raw pointer that points to the memory allocation with the same lifetime as the original data object.
This function is useful for mapping a pointer to a Rust structure into one of its fields.
Unlike map_ref and map_mut, the
map_ptr function does not borrow the original data. The by_ref
and
by_mut
functors should not dereference the pointers.
The original data remains unborrowed until you try to access the projection’s Cell data (e.g., using Cell::borrow_ref). At that point, both the original data and the projected data will be borrowed together.
§Examples
use std::ptr::{addr_of, addr_of_mut};
#[export]
pub struct Container {
field: usize,
}
let container = Cell::give(Origin::nil(), Container { field: 100 }).unwrap();
let mut projection = container
.map_ptr::<Container, usize>(
Origin::nil(),
// Safety: The `addr_of!` macros do not dereference the pointers,
// and the field of the structure cannot outlive its structure
// instance.
Some(|container| unsafe { addr_of!((*container).field) }),
Some(|container| unsafe { addr_of_mut!((*container).field) }),
)
.unwrap();
{
let mut projection = projection.clone();
let data_mut = projection.borrow_mut::<usize>(Origin::nil()).unwrap();
*data_mut += 50;
}
let data_ref = projection.borrow_ref::<usize>(Origin::nil()).unwrap();
assert_eq!(data_ref, &150);
The origin
parameter specifies the Rust or Script source code range
where the Cell’s data has been mapped.
The by_ref
and by_mut
parameters are functors that map the original
Cell’s pointer to another pointer (immutably and mutably, respectively).
The function returns a RuntimeError if the From
generic parameter is
not a type of the original Cell’s data, or if the Cell
points to an array of zero or more than one element (which can be
checked via the Cell::length function).
Even though map_ptr does not borrow the original data, the raw pointer
operations performed by the by_ref
and by_mut
functors require that
the pointer’s data does not have exclusive borrowings. Therefore, if the
original Cell’s data is borrowed mutably, this function will return an
error too.
The by_ref
and by_mut
parameters are optional. If you do not specify
one of them, the projection will be either read-only or write-only
(e.g., you will be able to access the data either mutably or immutably
depending on which functor has been specified). If both arguments are
None, the map_ptr function returns a Nil Cell.
§Safety
The map_ptr function is safe by itself, but the by_ref
and by_mut
functors are unsafe and must adhere to the following requirements:
- Neither of these functors should dereference the raw pointer specified in their argument.
- The mutability of the output pointer must match the mutability of the input pointer.
- The output pointer must address valid and fully initialized memory of
type
To
, and this memory must be valid for at least as long as the input memory. - The memory addresses of the output pointers of both functors must match.
Sourcepub fn map_slice(
self,
origin: Origin,
bounds: impl RangeBounds<usize>,
) -> RuntimeResult<Self>
pub fn map_slice( self, origin: Origin, bounds: impl RangeBounds<usize>, ) -> RuntimeResult<Self>
Creates a projection of the array to which this Cell points, mapping it to a slice of the array.
Using this function, you can access a single element or a range of elements within the array.
The origin
parameter specifies the range in the Rust or Script source
code where the Cell’s data has been mapped.
The bounds
parameter is a range of array indices that you want to map.
This range must be within the length of the underlying
array, unless the array consists of zero-sized (ZST) elements, in which
case the index bounds can be outside the array bounds.
If the bounds
argument specifies an invalid range (e.g., 20..10
), or
if the range is outside the array’s bounds (unless the Cell is a ZST
array), the function returns a RuntimeError.
The map_slice function does not borrow the original Cell’s pointer. Instead, it creates a new pointer to a subslice of the original memory allocation (similar to the map_ptr function). When you borrow the returned projection Cell, the Script Engine borrows both the original and projection Cells together.
Even though the map_slice function does not borrow the original data, if the data is currently borrowed mutably, the function will return an error.
Source§impl Cell
impl Cell
Sourcepub fn into_object(self) -> Object
pub fn into_object(self) -> Object
Converts a low-level Cell API into an Object, a higher-level Cell wrapper. This allows you to work with the Cell data as if it were an object of the ScriptType.