Noise Ser/  de
1 Serialization & Deserialization
1.1 Records
:
define-record
record-out
record-info?
1.2 Enumerations
define-enum
enum-out
enum-info?
1.3 Field Types
field-type?
Bool
Bytes
Float32
Float64
Int16
Int32
Varint
UInt16
UInt32
UVarint
String
Symbol
Delay
Hash  Table
Listof
Optional
String  Convertible
2 Backends
define-rpc
define-callout
serve
3 Callouts
callout-box?
make-callout-box
callout-box-install!
8.12

Noise Ser/de🔗ℹ

Bogdan Popa <bogdan@defn.io>

This is a companion package to Noise that provides utilities for serializing and deserializing data structures between Swift and Racket. Backwards-compatibility is not guaranteed.

1 Serialization & Deserialization🔗ℹ

 (require noise/serde) package: noise-serde-lib

1.1 Records🔗ℹ

A record is a data structure that is shared between Swift and Racket. In both languages, they are represented by structs. Use the raco command noise-serde-codegen to generate Swift definitions for records reachable from a given root module.

syntax

:

syntax

(define-record name
  field ...)
 
field = [field-id : field-type field-option ...]
  | [(field-id default-expr) : field-type field-option ...]
     
field-option = #:mutable
  | #:contract field-ctc-expr
 
  field-type : (or/c field-type? enum-info? record-info?)
Defines a record called name with the given set of fields. Records are backed by structs and generate smart constructors that take a keyword argument for every field. Smart constructors are named by prepending make- to the record name and bound at phase level 0. Record names must be unique across all modules.

When a field is #:mutable, it uses var as its introducer in Swift instead of let. The option currently has no effect on the generated Racket code.

Examples:
> (define-record Human
    [name : String]
    [age : UVarint #:contract (integer-in 0 125)]
    [(likes-pizza? #t) : Bool])
> (make-Human
   #:name "Bogdan"
   #:age 30)

(Human "Bogdan" 30 #t)

> Human

#<procedure:Human>

Changed in version 0.3 of package noise-serde-lib: Added the #:mutable field option.

syntax

(record-out id)

Exports the bindings associated with a record id.

procedure

(record-info? v)  boolean?

  v : any/c
Returns #t when v is a value containing runtime information about a record.

1.2 Enumerations🔗ℹ

An enumeration is a tagged union of product types. In Racket, enum variants are represented by individual structs that inherit from a common base. In Swift, they are represented using regular enums.

syntax

(define-enum name
  [variant-name variant-field ...] ...+)
 
variant-field = {field-id : field-type}
 
  field-type : (or/c field-type? enum-info? record-info?)
Defines an enumeration called name with the given set of variants. Enumeration names must be unique across all modules. In Swift, the variant-names and field-ids are converted to camel case.

Examples:
> (define-enum Result
    [ok]
    [err {message : String}])
> (Result? (Result.ok))

#t

> (Result? (Result.err "example"))

#t

> Result

#<enum-info>

syntax

(enum-out id)

Exports the bindings associated with an enum id.

procedure

(enum-info? v)  boolean?

  v : any/c
Returns #t when v is a value containing runtime information about an enumeration.

1.3 Field Types🔗ℹ

Field types control how individual values are serialized and deserialized.

procedure

(field-type? v)  boolean?

  v : any/c
Returns #t when v is a field type.

Field types for primitive values.

The Varint and UVarint field types serialize signed and unsigned integer values, respectively, using a variable-length encoding. In Swift, these values are represented by Int64 and UInt64.

syntax

(Delay t-expr)

 
  t-expr : (or/c field-type? enum-info? record-info?)
A constructor for a field type that delays the execution of t-expr until one of its methods is called. Use this to implement mutually-recursive data types.

A constructor for field types that represent hash tables composed of k keys and v values. In Swift, these values are represented by Dictionary values parameterized over the Swift representations of the k and v types, respectively.

When k is an enum or a record type, the enum or record must be extended to implement the Hashable protocol in Swift.

procedure

(Listof t)  field-type?

  t : (or/c field-type? enum-info? record-info?)
A constructor for field types that represent lists composed of field-type values. In Swift, these values are represented by arrays of the subtype.

procedure

(Optional t)  field-type?

  t : (or/c field-type? enum-info? record-info?)
A constructor for optional field types.

procedure

(StringConvertible string-> ->string)  field-type?

  string-> : (-> string? any/c)
  ->string : (-> any/c string?)
Like String, but converts the value to a string using ->string before sending it to a client and converts strings to values using string-> when receiving data from a client.

2 Backends🔗ℹ

 (require noise/backend) package: noise-serde-lib

The noise/backend module has an internal handler registry that is used to map remote procedure call ids to handler procedures.

syntax

(define-rpc (id arg ... maybe-response-type)
  body ...+)
 
arg = [arg-label arg-id : arg-type-expr]
     
maybe-response-type = 
  | : response-type-expr
 
  arg-type-expr : (or/c field-type? enum-info? record-info?)
  response-type-expr : (or/c field-type? enum-info? record-info?)
Defines a procedure named id and registers an RPC handler for it in the handler registry. RPC ids must be unique across all modules. The procedure is automatically provided in a submodule of the enclosing module named rpc.

The noise-serde-codegen command automatically generates Swift code to handle calling these procedures. In Swift, the RPC id, arg-labels and arg-ids are converted to camel case. The arg-labels have no meaning in Racket.

Examples:
> (define-rpc (do-nothing)
    (void))
> (do-nothing)
> (define-rpc (get-human-name [of h : Human] : String)
    (Human-name h))
> (get-human-name (make-Human #:name "Bogdan" #:age 30))

"Bogdan"

syntax

(define-callout (id arg ...))

 
arg = [arg-id : arg-type-expr]
 
  arg-type-expr : (or/c field-type? enum-info? record-info?)
Defines a foreign procedure named id. Callout ids must be unique across all modules.

The noise-serde-codegen command automatically generates Swift code to handle installing Swift procedures for each callout.

Example:
> (define-callout (hello-cb [h : Human]))

In Racket, the above example binds a procedure named hello that may be called with a Human value in order to execute a Swift procedure. In Swift, the generated backend will contain a procedure with the following signature:

installCallback(helloCb: @escaping (Human) -> Void) -> Future<String, Void>

That procedure can be used to install a callback from the Swift side. Executing a callout on the Racket side before it’s been installed from the Swift side raises an exception.

Currently, the buffer used to decode data sent back to Swift via a callout is limited to 8KiB, so avoid sending large payloads. If necessary, have the Swift side call a regular RPC when it receives a callout instead.

procedure

(serve in-fd out-fd)  (-> void?)

  in-fd : exact-integer?
  out-fd : exact-integer?
Converts the file descriptors represented by in-fd and out-fd to an input port and an output port, respectively, then spawns a thread that reads requests from the input port in the form of records. Request handlers are defined using define-rpc. Handlers are run in their own threads and multiple requests may be handled concurrently.

Returns a procedure that stops the server when applied.

3 Callouts🔗ℹ

 (require noise/unsafe/callout) package: noise-serde-lib

The noise/unsafe/callout module provides a facility for converting function pointer addresses to callable Racket procedures via the FFI. A callout box is a two-element struct containing a C function type and an optional Racket procedure of that type. Callout boxes start out empty and must be filled via a call to callout-box-install!. Callout boxes are themselves callable once filled.

Warning: these operations are inherently unsafe and you must take care that the function pointers installed in a box are kept immobile during the lifetime of that box.

procedure

(callout-box? v)  boolean?

  v : any/c
Returns #t when v is a callout box.

procedure

(make-callout-box fun-type)  callout-box?

  fun-type : ctype?
Returns a callout box with the given function type.

procedure

(callout-box-install! b p)  void?

  b : callout-box?
  p : exact-integer?
Installs the function pointer located at address p in b.