--- title: "Getting started with RZig" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Getting started with RZig} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") library(rzig) compiler <- find_zig(required = FALSE) has_zig <- isTRUE(compiler$supported) ``` RZig lets an R package implement functions in Zig. The package author writes ordinary Zig functions and marks the exported ones; RZig generates the registration, the R wrappers and the conversion of every argument and result, and runs the Zig build when the package is installed. This vignette goes through the workflow one step at a time. Every step up to the installation is plain R and runs without Zig; the steps that compile run when a supported Zig compiler is found, and this copy of the vignette `r if (has_zig) "ran" else "skipped"` them. ## Find the compiler `find_zig()` looks for a Zig release of the supported series (0.16 and 0.17) in `ZIG`, on `PATH`, in the folder of `install_zig()` and in the usual install locations. With `required = FALSE`, a missing compiler is a result and not an error: ```{r find} compiler ``` A user without Zig can install a verified release with `install_zig()`, which downloads it into the user cache folder of rzig after asking for permission. ## Create a package `create_rzig_package()` creates a new package with the RZig scaffold, a starter function and a test; `use_rzig()` adds the same scaffold to an existing package. The package here lives in R's temporary directory: ```{r create} path <- file.path(tempdir(), "hellozig") create_rzig_package(path, fields = list( Title = "Greetings from Zig", Description = "Shows the workflow of 'rzig'." )) ``` The Zig code lives in `src/rzig/src/main.zig`. The starter function shows the three parts of an export: documentation comments, which become the help page, the marker `/// @export`, and a public function with supported types: ```{r starter, comment = ""} main_zig <- file.path(path, "src", "rzig", "src", "main.zig") cat(readLines(main_zig), sep = "\n") ``` ## Add a function A second export takes a numeric vector, which Zig reads as a borrowed slice, and returns a numeric vector that Zig allocates in the call context `ctx`: ```{r add, comment = ""} cat( "", "/// The cumulative sum of a numeric vector.", "/// @param x A numeric vector.", "/// @return A numeric vector of the same length.", "/// @examples", "/// cumulative(c(1, 2, 3))", "/// @export", "pub fn cumulative(ctx: *rzig.Ctx, x: []const f64) rzig.Error![]f64 {", " const out = try ctx.alloc(f64, x.len);", " var total: f64 = 0;", " for (x, out) |value, *slot| {", " total += value;", " slot.* = total;", " }", " return out;", "}", sep = "\n", file = main_zig, append = TRUE ) ``` ## Scan, generate and check `scan_exports()` lists the exported functions with their R arguments and the R values they accept and return: ```{r scan} scan_exports(path) ``` `document()` writes the generated files: the Zig manifest, the R wrappers, the `NAMESPACE` lines and the help pages. Run it after every change of an exported signature or its documentation: ```{r document} bindings <- document(path) cat(bindings$wrappers) ``` `rzig_status()` runs the checks that `build_zig()` runs before it compiles: the `DESCRIPTION`, the scaffold, the Zig source, the types of the exports, the generated files and the toolchain: ```{r status} rzig_status(path) ``` ## Build and call `build_zig()` installs the package from a copy, so that the source folder stays clean, and reports compiler errors by file and line: ```{r build, eval = has_zig} build <- build_zig(path) build library(build$package, character.only = TRUE, lib.loc = build$library) hello_zig("R") cumulative(c(1, 2, 3, 4)) ``` Arguments of the wrong type are R errors that name the argument; the R session continues: ```{r errors, eval = has_zig} try(cumulative("a")) ``` ## Test the Zig code Functions that do not use the `rzig` module can be tested with Zig's own test runner, without R. `test_zig()` runs the `test` blocks reached from `src/rzig/src/tests.zig`: ```{r zig-tests, comment = ""} writeLines(c( "const std = @import(\"std\");", "", "fn add(a: f64, b: f64) f64 {", " return a + b;", "}", "", "test \"add\" {", " try std.testing.expectEqual(@as(f64, 3), add(1, 2));", "}" ), file.path(path, "src", "rzig", "src", "tests.zig")) ``` ```{r run-zig-tests, eval = has_zig} tests <- test_zig(path) tests ``` ## Try Zig from the R prompt `zig_function()` compiles one Zig function into a temporary package and returns an R function, which suits experiments before code moves into a package: ```{r zig-function, eval = has_zig} twice <- zig_function("pub fn twice(x: f64) f64 { return 2 * x; }") twice(21) ``` ```{r cleanup, include = FALSE} if (has_zig) unloadNamespace("hellozig") unlink(path, recursive = TRUE) ``` ## Next steps The README of rzig lists the supported types. The other vignettes describe updating the scaffold, exports and classes, threads and random numbers, numerical routines from R, packages that share code, and distribution.