Noise Ser/de
This is a companion package to Noise that provides utilities for serializing and deserializing data structures between Swift and Racket.
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 ...)
name = id | (id : protocol-id ...+) 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?)
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.
> (define-record (Human : Equatable Hashable) [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.
Changed in version 0.8: Added protocol support.
syntax
(record-out id)
procedure
(record-info? v) → boolean?
v : any/c
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 ...] ...+)
name = id | (id : protocol-id ...+) variant-field = {field-id : field-type}
field-type : (or/c field-type? enum-info? record-info?)
> (define-enum Result [ok] [err {message : String}]) > (Result? (Result.ok)) #t
> (Result? (Result.err "example")) #t
> Result #<enum-info>
Changed in version 0.8 of package noise-serde-lib: Added protocol support.
syntax
(enum-out id)
procedure
(enum-info? v) → boolean?
v : any/c
1.3 Field Types
Field types control how individual values are serialized and deserialized.
procedure
(field-type? v) → boolean?
v : any/c
value
value
value
value
value
value
value
value
value
value
value
value
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?)
procedure
(HashTable k v) → field-type?
k : (or/c field-type? enum-info? record-info?) v : (or/c field-type? enum-info? record-info?)
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?)
procedure
(Optional t) → field-type?
t : (or/c field-type? enum-info? record-info?)
procedure
(StringConvertible string-> ->string) → field-type?
string-> : (-> string? any/c) ->string : (-> any/c string?)
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?)
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.
> (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?)
The noise-serde-codegen command automatically generates Swift code to handle installing Swift procedures for each callout.
> (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
in-fd : exact-integer? out-fd : exact-integer?
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
procedure
(make-callout-box fun-type) → callout-box?
fun-type : ctype?
procedure
(callout-box-install! b p) → void?
b : callout-box? p : exact-integer?