Struct Cell

Source
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

Source

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

Source

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.

Source

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.

Source

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.

Source

pub fn is_nil(&self) -> bool

Returns true if this Cell is Cell::nil.

For example, if you give a unit () value, the resulting Cell is a Nil Cell.

Source

pub fn origin(&self) -> Origin

Returns the Rust or Script source code range indicating where the Cell’s data was created.

Source

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.

Source

pub fn is<T: ScriptType + ?Sized>(&self) -> bool

Returns true if the Cell’s data has a type specified by the T generic parameter.

Note that if the Cell stores a string, the function will return true for both str and u8 types.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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).

Source

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.

Source

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:

  1. Neither of these functors should dereference the raw pointer specified in their argument.
  2. The mutability of the output pointer must match the mutability of the input pointer.
  3. 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.
  4. The memory addresses of the output pointers of both functors must match.
Source

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

Source

pub fn type_match(&self) -> TypeMatch<'_>

Provides a convenient helper interface for handling Cell data based on the Cell type.

For more details, see TypeMatch.

Source§

impl Cell

Source

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.

Trait Implementations§

Source§

impl<'a> AsRef<Cell> for Provider<'a>

Source§

fn as_ref(&self) -> &Cell

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl Clone for Cell

Source§

fn clone(&self) -> Cell

Returns a duplicate of the value. Read more
1.0.0 · Source§

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

Performs copy-assignment from source. Read more
Source§

impl Debug for Cell

Source§

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

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

impl Default for Cell

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl Freeze for Cell

§

impl !RefUnwindSafe for Cell

§

impl Send for Cell

§

impl Sync for Cell

§

impl Unpin for Cell

§

impl !UnwindSafe for Cell

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
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.