silk.mem.pointer.
Raw data pointers: make them, check them for null, move along them, and read and write through them.
When to use#
Use raw pointers at foreign function boundaries and in storage that native code owns. Inside
Silk code, use references (&T, &mut T) and slices (&[T]). The compiler checks those, but
it does not check raw pointers.
Details#
The pointer types are:
*const Tand*mut T: one element, never null.[*]const Tand[*]mut T: many elements, never null. The pointer has no length.?*const T,?[*]mut T, and so on: the same types, but null is allowed.*const align(1) T: a pointer that is not aligned forT. UsePointer.readUnalignedandPointer.writeUnalignedwith it.
A pointer is Copy. It does not own its target, it does not borrow it, and it does not keep it
alive. Making, comparing, and converting pointers is safe. Reading and writing through a pointer
is unsafe.
Gotchas#
A pointer that is not null can still point to memory that was released or that is not
initialized. Each unsafe operation lists what the caller must prove. If the caller is wrong,
the behavior is undefined.
Examples#
Write and read through pointers to a local value and a slice#
import silk.core.option { Option }
import silk.mem.pointer { Pointer }
fn third(values: &[i32]) -> i32 {
return match move Pointer.nonNullMany<i32>(Pointer.fromSlice<i32>(values)) {
Option<[*]const i32>.Some { value: first } => unsafe Pointer.read<i32>(
unsafe Pointer.at<i32>(first, 2)
)
Option<[*]const i32>.None => 0
}
}
pub fn main() -> i32 {
let mut value: i32 = 1
let target = Pointer.fromMutRef<i32>(&mut value)
unsafe {
Pointer.write<i32>(target, 12)
}
let values = [10, 20, 30]
if value + third(&values) != 42 {
return 1
}
return 0
}Import as Pointer with import silk.mem.pointer { Pointer }.
Public declarations: 1.
Pointer#
pub struct PointerThe owner type of the pointer operations. It has no fields and is never constructed.
When to use#
Use raw pointers at foreign function boundaries and in storage that native code owns. Inside
Silk code, use references (&T, &mut T) and slices (&[T]). The compiler checks those, but
it does not check raw pointers.
Details#
A pointer is Copy. It does not own its target, it does not borrow it, and it does not keep it
alive. Making, comparing, and converting pointers is safe. Reading and writing through a
pointer is unsafe.
Gotchas#
A pointer that is not null can still point to memory that was released or that is not
initialized. Each unsafe operation lists what the caller must prove. If the caller is wrong,
the behavior is undefined.
Implementation Pointer#
impl PointerAssociated function bytes#
pub unsafe fn bytes<T>(pointer: ?*const T) -> ?[*]const u8Returns pointer as a pointer to bytes. It does not read memory.
When to use#
Use bytes to read the first fields of a foreign record whose full size is not known, without
reading the whole T.
Gotchas#
The result is null if pointer is null. It has no length and does not keep the memory alive.
Each byte that you read through it must be readable and initialized.
Associated function address#
pub fn address<T>(pointer: ?*const T) -> usizeReturns the address of pointer as a number. Null gives 0.
Details#
It does not read memory. The result has the pointer width of the target. You cannot make a pointer from the result.
Associated function addressMany#
pub fn addressMany<T>(pointer: ?[*]const T) -> usizeReturns the address of pointer as a number. Null gives 0.
Details#
It does not read memory. The result has the pointer width of the target. You cannot make a pointer from the result.
Associated function null#
pub fn null<T>() -> ?*mut TReturns a null ?*mut T.
Associated function isNull#
pub fn isNull<T>(pointer: ?*const T) -> boolReturns true if pointer is null.
Associated function isNullMany#
pub fn isNullMany<T>(pointer: ?[*]const T) -> boolReturns true if the many-element pointer is null.
Associated function nonNull#
pub fn nonNull<T>(pointer: ?*const T) -> Option<*const T>Returns the non-null form of pointer. Returns an absent option if pointer is null.
Gotchas#
A present result does not prove that the memory is alive or initialized.
Associated function nonNullMut#
pub fn nonNullMut<T>(pointer: ?*mut T) -> Option<*mut T>Returns the non-null form of pointer. Returns an absent option if pointer is null.
Gotchas#
A present result does not prove that the memory is alive or initialized.
Associated function nonNullMany#
pub fn nonNullMany<T>(pointer: ?[*]const T) -> Option<[*]const T>Returns the non-null form of pointer. Returns an absent option if pointer is null.
Gotchas#
A present result does not prove that the memory is alive or initialized. The result has no length.
Associated function nonNullManyMut#
pub fn nonNullManyMut<T>(pointer: ?[*]mut T) -> Option<[*]mut T>Returns the non-null form of pointer. Returns an absent option if pointer is null.
Gotchas#
A present result does not prove that the memory is alive or initialized. The result has no length.
Associated function fromRef#
pub fn fromRef<T>(value: &T) -> *const TReturns a pointer to the value that value refers to.
Gotchas#
The borrow ends when this call returns. The pointer does not keep the value alive or prevent other access to it. Each later use must make sure that the value is still alive.
Associated function fromMutRef#
pub fn fromMutRef<T>(value: &mut T) -> *mut TReturns a pointer to the value that value refers to.
Gotchas#
The borrow ends when this call returns. The pointer does not keep the value alive or prevent other access to it. Each later use must make sure that the value is still alive.
Associated function fromSlice#
pub fn fromSlice<T>(values: &[T]) -> ?[*]const TReturns a pointer to the first element of values.
Gotchas#
The pointer has no length. Pass values.length to the code that uses it. The result type
allows null, so use Pointer.nonNullMany before you index it. The pointer does not keep the
slice alive.
Associated function fromMutSlice#
pub fn fromMutSlice<T>(values: &mut [T]) -> ?[*]mut TReturns a pointer to the first element of values.
Gotchas#
The pointer has no length. Pass values.length to the code that uses it. The result type
allows null, so use Pointer.nonNullManyMut before you index it. The pointer does not keep
the slice alive.
Associated function at#
pub unsafe fn at<T>(pointer: [*]const T, index: usize) -> *const TReturns a pointer to the element at index of pointer.
Gotchas#
The caller must prove that index is inside the same live allocation. It does not check
bounds.
Associated function atMut#
pub unsafe fn atMut<T>(pointer: [*]mut T, index: usize) -> *mut TReturns a writable pointer to the element at index of pointer.
Gotchas#
The caller must prove that index is inside the same live, writable allocation. It does not
check bounds.
Associated function read#
pub unsafe fn read<T: Copy>(pointer: *const T) -> TReturns a copy of the T at pointer. The pointer must be aligned for T.
Gotchas#
The caller must prove that pointer points to a live, readable, initialized T. For storage
that is not aligned, use Pointer.readUnaligned.
Associated function write#
pub unsafe fn write<T: Copy>(pointer: *mut T, value: T) -> ()Writes value to the T at pointer. The pointer must be aligned for T.
Gotchas#
The caller must prove that pointer points to live, writable storage for a T. For a T
that is not Copy, use Pointer.put.
Associated function take#
pub unsafe fn take<T>(pointer: *mut T) -> TMoves the T out of the storage at pointer and returns it.
Details#
T does not need to be Copy. After the call, the caller owns the value and the storage is
uninitialized. Fill it again with Pointer.put, or do not read it again.
Gotchas#
The caller must prove that pointer points to a live, initialized T that no other code will
take or drop. Otherwise the value is dropped two times.
Associated function dropInPlace#
pub unsafe fn dropInPlace<T>(pointer: *mut T) -> ()Drops the T at pointer in place and leaves the storage uninitialized.
When to use#
Use dropInPlace to destroy a value whose storage is released next, such as a heap cell. It
runs the drop on the pointee directly. drop Pointer.take(pointer) copies the value out first.
Gotchas#
The caller must prove that pointer points to a live, initialized T that no other code will
take or drop. Otherwise the value is dropped two times.
Associated function put#
pub unsafe fn put<T>(pointer: *mut T, value: T) -> ()Moves value into the storage at pointer. It does not drop the value that was there.
Details#
T does not need to be Copy. The storage owns value after the call.
Gotchas#
The caller must prove that pointer points to live, writable storage for a T. If the
storage holds an initialized value, that value is not dropped and leaks.
Associated function borrow#
pub unsafe fn borrow<'a, A, T>(anchor: &'a A, pointer: *const T) -> &'a TReturns a shared reference to the T at pointer that lives as long as the borrow of
anchor.
When to use#
Use it in a handle type that owns storage through a pointer. The handle passes itself as
anchor, so the reference cannot outlive the handle. Arc.get works this way.
Gotchas#
The caller must prove that pointer points to an initialized T that stays valid while
anchor is borrowed, and that no code changes it during that time, except through atomic
operations.
Associated function borrowMut#
pub unsafe fn borrowMut<'a, A, T>(anchor: &'a mut A, pointer: *mut T) -> &'a mut TReturns an exclusive reference to the T at pointer that lives as long as the exclusive
borrow of anchor.
When to use#
Use it in a guard type. The guard passes itself as anchor, so the reference cannot outlive
the guard. MutexGuard.get works this way.
Gotchas#
The caller must prove that pointer points to an initialized T that stays valid while
anchor is borrowed, and that no other code reads or writes it during that time.
Associated function borrowMany#
pub unsafe fn borrowMany<'a, A, T>(anchor: &'a A, pointer: [*]const T, length: usize) -> &'a [T]Returns a shared slice of the length elements that start at pointer. The slice lives as
long as the borrow of anchor.
When to use#
Use it in a handle type that keeps elements in storage it reaches through a pointer, such as
bytes stored inline after a header. The handle passes itself as anchor, so the slice cannot
outlive the handle.
Details#
anchor only sets the lifetime of the result. It is not read. The call does not check the
pointer or the length, so the result costs the same as a slice that you already have.
Gotchas#
The caller must prove all of these:
pointerpoints tolengthinitializedTvalues, one after another in one allocation. The pointer must be aligned forT, also whenlengthis 0.lengthtimes the size ofTfits inisize.The values stay valid while
anchoris borrowed, and no code changes them during that time, except through atomic operations.
Examples#
import silk.core.option { Option }
import silk.mem.pointer { Pointer }
struct Inline {
bytes: [u8; 4],
}
fn view<'a>(cell: &'a Inline) -> &'a [u8] {
let first = match move Pointer.nonNullMany<u8>(Pointer.fromSlice<u8>(&cell.bytes)) {
Option<[*]const u8>.Some { value } => value
Option<[*]const u8>.None => Intrinsic.trap("an array is never at null")
}
unsafe { return Pointer.borrowMany<Inline, u8>(cell, first, 3) }
}
pub fn main() -> i32 {
let cell = Inline { bytes: [1, 2, 3, 4] }
let bytes = view(&cell)
if bytes.length != 3 || bytes[2] != 3 {
return 1
}
return 0
}Associated function borrowManyMut#
pub unsafe fn borrowManyMut<'a, A, T>(anchor: &'a mut A, pointer: [*]mut T, length: usize) -> &'a mut [T]Returns an exclusive slice of the length elements that start at pointer. The slice lives
as long as the exclusive borrow of anchor.
When to use#
Use it in a handle or guard type that keeps elements in storage it reaches through a pointer.
The handle passes itself as anchor, so the slice cannot outlive the handle, and nothing else
can use the handle while the slice lives.
Details#
anchor only sets the lifetime of the result. It is not read. The call does not check the
pointer or the length, so the result costs the same as a slice that you already have.
Gotchas#
The caller must prove all of these:
pointerpoints tolengthinitializedTvalues, one after another in one allocation. The pointer must be aligned forT, also whenlengthis 0.lengthtimes the size ofTfits inisize.The values stay valid while
anchoris borrowed, and no other code reads or writes them during that time.
Associated function readUnaligned#
pub unsafe fn readUnaligned<T: Copy>(pointer: *const align(1) T) -> TReturns a copy of the T at pointer. The pointer does not need to be aligned.
Gotchas#
The caller must prove that pointer points to a live, readable, initialized T.
Associated function writeUnaligned#
pub unsafe fn writeUnaligned<T: Copy>(pointer: *mut align(1) T, value: T) -> ()Writes value to the T at pointer. The pointer does not need to be aligned.
Gotchas#
The caller must prove that pointer points to live, writable storage for a T.
Associated function assumeAligned#
pub unsafe fn assumeAligned<T>(pointer: *const align(1) T) -> *const TReturns pointer as a pointer that is aligned for T. The address does not change.
Gotchas#
The caller must prove that the address is a multiple of the alignment of T. It does not
check.
Associated function assumeMany#
pub unsafe fn assumeMany<T>(pointer: *const T) -> [*]const TReturns pointer as a many-element pointer. The address does not change.
Gotchas#
The result has no length. Before each indexed access, the caller must prove that the element is inside the allocation.
Associated function assumeAlignedMut#
pub unsafe fn assumeAlignedMut<T>(pointer: *mut align(1) T) -> *mut TReturns pointer as a pointer that is aligned for T. The address does not change.
Gotchas#
The caller must prove that the address is a multiple of the alignment of T. It does not
check.
Associated function assumeManyMut#
pub unsafe fn assumeManyMut<T>(pointer: *mut T) -> [*]mut TReturns pointer as a many-element pointer. The address does not change.
Gotchas#
The result has no length. Before each indexed access, the caller must prove that the element is inside the allocation.
Category: Memory.
Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2.