On this page:
*
/
GCable_  ptr_  t
#%index
new
malloc
free
mem
cast
memcpy
memmove
memset
ptr_  to_  uintptr
uintptr_  to_  ptr
ptr_  to_  cpointer
cpointer_  to_  ptr
0.45+9.2.0.3

2.5 Foreign Pointers🔗ℹ

A pointer object encapsulates a memory address and a potentially empty sequence of symbolic tags. A pointer object may refer to an address that under the control of Racket’s memory manager and garbage collection, or it may refer to an address is managed externally.

The base type for pointers is ptr_t (equivalent to void_t*), and new pointer types can be created via foreign.type with either a name for an opaque content type, an existing pointer type, a struct type or a union type.

A pointer’s tags enable sanity checking that the right kind of pointer is provided to an operation. If an operation expects a pointer with a certain sequence of tags, it accepts a pointer with additional tags added to the end, so additional tags form a pointer subtype. A ptr_t representation has no tags, which means that a C conversion to Rhombus via ptr_t is not accepted by any context that expects some tag, whereas a pointer with any tags is accepted as a ptr_t representation to translate to C. Whether a pointer object represents an address managed by Rhombus’s garbage collector is independent of its tags.

foreign type

type *

 

foreign type

type /gcable

 

annotation

GCable_ptr_t

The * postfix type operator describes a pointer type that is tagged with the name of type with a * suffix. If type is a pointer type, then its “name” for this purpose is the tag used for its pointers. Otherwise, it is the name as defined via foreign.type. If type is a struct or union type, the new type type* gets the same static information as type.

The /gcable type operator (more precisely, a / operator that expects a subsequent literal gcable always) requires that the argument type is a pointer type, and it describes a type that is the same, but that represents an address within memory that is managed by Rhombus’s garbage collector. The Rhombus-to-C conversion of a /gcable pointer is no different that for the original pointer type (i.e., it is not required to refer to a garbage-collectable address), but it affects the handling of a C address representation to a Rhombus representation.

The GCable_ptr_t annotation is satisfied only by pointer objects that are allowed to reference memory that is managed Racket’s garbage collector. In contrast, foreign.type ptr_t/gcable as an annotation is satisified by any pointer object, since ptr_t/gcable accepts any pointer for conversion to C.

When an expression has the static information of the pointer type, then indexing via [] (i.e., #%index) or mem extracts a Rhombus representation for type, and indexing with [] or mem plus := assigns to the pointer by converting a Rhombus representation of type to a C representation to install. A pointer expression works with [] only via static information.

foreign type

elem_type #%index [size_expr]

The #%index infix operator is an implicit form and not usually written explicitly: elem_type[size_expr] is equivalent to elem_type #%index [size_expr],

Describes a type that is represented by an array on the C side and a pointer object in the Rhombus side. In most type contexts, the array’s size_expr must be a literal nonnegative integer. When a type using [] is used with new, however, then size_expr can be any expression that produces a nonnegative integer.

The Rhombus-side pointer representation uses a tag formed by adding a * suffix on the name of elem_type, as long as it has a name. If elem_type is an immediate struct, union, or -> form, then it has no name, and the Rhombus-side representation is a generic pointer.

When an expression has the static information of an array type, then indexing via [] (i.e., #%index) or mem extracts a Rhombus representation for an element of the array, and indexing with [] or mem plus := assigns to the array by converting a Rhombus value to a C representation to install into the array. If size_expr is a literal integer, then the bounds checking prevents indexing with positions that are negative or not less than the size. An array-pointer expression works with [] only via static information.

> sizeof(double_t[3])

24

> def p = new double_t[3]

> p

#<double_t*/gcable>

> p[0] := 0.0

> p[1] := 10.0

> p[2] := 20.0

> p[1]

10.0

> mem p[3]

mem: value does not satisfy annotation

  annotation: Int.in(0 .. 3)

  value: 3

expression

new maybe_mode type

 

expression

new maybe_mode type(field_expr, ...)

 

expression

new maybe_mode type(variant_id: field_expr)

 

maybe_mode

 = 

~manual

 | 

~gcable

 | 

~immobile

 | 

~traced

 | 

~traced_immobile

 | 

ϵ

Allocates memory. A type(field_expr, ...) form is allowed only when type is a struct type, and a type(variant_id: field_expr) form is allowed only when type is a union type.

The amount of allocated memory depends on type, where the allocated memory spans as many bytes as the C representation of type. A type of the form elem_type[expr] is allowed with a non-literal expr to allocate the indicated multiple of the C size of elem_type in bytes.

By default, allocation uses ~gcable mode, but a maybe_mode specification can pick any of the supported modes:

  • ~gcable: Allocates in Rhombus’s garbage-collected space. The allocated memory becomes eligible for garbage collection when it is not referenced by any reachable pointer object or traced allocated memory. Even before collection, the memory manager may relocate the object, but garbage collection or relocation cannot happen with a foreign-procedure call is active.

  • ~immobile: Like ~gcable, but the allocated memory will not be relocated by a different address by the memory manager as long as it is not collected.

  • ~traced: Like ~gcable, but the allocated memory is traced, meaning that it can itself contain references to other allocated memory. The references are updated by the memory manager if it moved the referenced objects.

  • ~traced_immobile: Like ~traced, but the allocated memory will not be relocated by a different address by the memory manager as long as it is not collected.

  • ~manual: Allocates outside of Rhombus’s garbage-collected space. The allocated memory is never relocated by the garbage collection, and it must be freed explicitly with free.

> def p1 = new int_t

> mem *p1 := 3

> mem *p1

3

> def pm = new ~manual int_t

> mem *pm := 4

> free(pm)

> def p3 = new int_t[1+2]

> p3[2] := 20

> p3[2]

20

> def p1_imm = new ~immobile int_t

> def i1_imm = ptr_to_uintptr(p1_imm)

> memory.GC() // probably moves p1, does not move p1_imm

> i1_imm == ptr_to_uintptr(p1_imm)

#true

> def pp = new ~traced int_t*

> pp[0]

#<int_t*>

> pp[0] := new int_t

> pp[0][0] := 5

> memory.GC() // probably moves pp[0]

> pp[0][0]

5

> def mutable pm = new ~manual int_t

> pm[0] := 6

> def pm_i = ptr_to_uintptr(pm)

> pm := #false

> memory.GC() // does not affect manual allocation

> pm := uintptr_to_ptr(pm_i)

> pm[0]

6

> free(pm)

expression

malloc maybe_mode maybe_as (size_expr)

 

maybe_as

 = 

~as to_type

 | 

ϵ

Allocates memory in the same way as new, but where size_expr specifies a size in bytes.

If ~as to_type is specified, the result pointer is tagged as to_type, where to_type must be a pointer type, and the malloc expression has the static information of to_type.

> def gp = malloc(64)

> gp

#<ptr_t/gcable>

> gp is_a foreign.type int_t*

#false

> def p = malloc ~as int_t* (64)

> p is_a foreign.type int_t*

#true

> mem *p := 5

> mem *p

5

foreign.struct Point_t(x :: int_t,

                       y :: int_t)

> def pt0 = malloc(sizeof(Point_t))

> pt0

#<ptr_t/gcable>

> pt0 is_a Point_t

#false

> def pt = malloc ~as Point_t* (sizeof(Point_t))

> pt is_a Point_t

#true

> pt.x := 1

> pt.y := 2

> pt.x + pt.y

3

function

fun free(p :: ptr_t) :: Void

Deallocates memory that was allocated with new or malloc in ~manual mode.

expression

mem *ptr_expr

 

expression

mem *ptr_expr := val_expr

 

expression

mem *(ptr_type)ptr_expr

 

expression

mem *(ptr_type)ptr_expr := val_expr

 

expression

mem ptr_expr[index_expr]

 

expression

mem ptr_expr[index_expr] := val_expr

 

expression

mem & ptr_expr[delta_expr]

Extracts a Rhombus representation for an inferred type of the C representation stored at the address represented by the pointer result of ptr_expr; updates the C representation stored at the address represented by ptr_expr with a value converted from a Rhombus representation produced by val_expr; or, in the case of mem &, shifts the address from ptr_expr by delta_expr times the size of type.

When the *(ptr_type)ptr_expr form is used, the tags on the result of ptr_expr are ignored. The pointer is effectively cast to ptr_type, and ptr_type must be a pointer type referring to type elements.

When the * ptr_expr, ptr_expr[index_expr], or & ptr_expr[delta_expr] form is used, then ptr_expr must have static information to indicate an element type or pointer-referenced type to be used as type. The mem ptr_expr[index_expr] form is equivalent to ptr_expr[index_expr] without mem, since ptr_expr must have static information that would also allow it to work with [] via #%index.

> def p = new double_t

> p

#<double_t*/gcable>

> mem p[0] := 0.0

> mem p[0]

0.0

> mem *(int64_t*)p

0

> mem *(int64_t*)p := -1

> mem p[0]

#nan

> mem (mem &p[-3])[3]

#nan

expression

cast maybe_from_type maybe_to (to_type) maybe_offset expr

 

maybe_from_type

 = 

~from from_type

 | 

ϵ

 

maybe_to

 = 

~to

 | 

ϵ

 

maybe_offset

 = 

~offset (offset_expr)

 | 

ϵ

Converts the Rhombus representation produced by expr from one type’s representation to another. If from_type or is not specified, it defaults to ptr_t. The C representations for both from_type and to_type must be both addresses or both scalars, and conversions may apply to the Rhombus representation produced by expr (based on from_type) or the converted result (based on to_type) of the cast address.

If ~offset (offset_expr) is specified, then the (pre-conversion) address produced by the cast is offset_expr bytes after the address represented by the converted result of expr. A ~offset (offset_expr) is allowed only when to_type has an address representation.

> def p_i = new int16_t

> def p_b = cast (byte_t*)p_i

> mem p_b[0] := 1

> mem p_b[1] := 1

> mem *p_i

257

> mem *(cast (byte_t*) ~offset(1) p_i) := 2

> mem p_b[0] := mem p_b[1]

> mem *p_i

514

> cast ~from (int32_t*) ~to (byte_t*) p_i

cast: value does not match type

  value: #<int16_t*/gcable>

  type: int32_t*

> cast ~from (int16_t*) ~to (byte_t*) p_i

#<byte_t*/gcable>

function

fun memcpy(

  dest :: ptr_t,

  src :: ptr_t,

  len :: NonnegInt,

  ~dest_offset: dest_offset :: Int = 0,

  ~src_offset: src_offset :: Int = 0

) :: Void

 

function

fun memmove(

  dest :: ptr_t,

  src :: ptr_t,

  len :: NonnegInt,

  ~dest_offset: dest_offset :: Int = 0,

  ~src_offset: src_offset :: Int = 0

) :: Void

 

function

fun memset(

  dest :: ptr_t,

  byte :: Byte,

  len :: NonnegInt,

  ~dest_offset: dest_offset :: Int = 0

) :: Void

The memcpy and memmove functions copy len bytes from the address represented by src plus src_offset to the address represented by dest plus dest_pffset. In the case of memcpy, the source and destination regions must not overlap.

The memcpy function sets len bytes at the address represented by dest plus dest_pffset so that each byte’s value is byte.

> def bstr1 = #"Hello".copy()

> def bstr2 = #"Goodbye".copy()

> memcpy(cast ~from (bytes_ptr_t) ~to (ptr_t) bstr1,

         cast ~from (bytes_ptr_t) ~to (ptr_t) bstr2,

         2)

> bstr1

#"Gollo".copy()

> memmove(mem &(cast ~from (bytes_ptr_t) ~to (byte_t*) bstr1)[2],

          cast ~from (bytes_ptr_t) ~to (ptr_t) bstr1,

          3)

> bstr1

#"GoGol".copy()

> memset(cast ~from (bytes_ptr_t) ~to (ptr_t) bstr1,

         Char"o".to_int(),

         3,

         ~dest_offset: 2)

> bstr1

#"Goooo".copy()

function

fun ptr_to_uintptr(ptr :: ptr_t) :: uintptr_t

 

function

fun uintptr_to_ptr(addr :: uintptr_t) :: ptr_t

Conversions between addresses represented as pointer objects and addresses represented as integers.

Beware that the integer form of an address managed by Rhombus’s garbage collector can become immediately invalid, unless an object at the address was allocated as immobile.

function

fun ptr_to_cpointer(ptr :: ptr_t) :: Any

 

function

fun cpointer_to_ptr(addr :: uintptr_t) :: ptr_t

Conversions between addresses represented as pointer objects and the representation used by ffi/unsafe.