juliabridge

Julia setup for R packages that wrap a Julia engine.

juliabridge collects the patterns you otherwise learn the hard way when building an R package that calls Julia:

It is small and opinionated. In each consuming package it replaces about 100 lines of brittle boilerplate with about 5.

Backend

juliabridge runs Julia through JuliaConnectoR, in a separate process from R. The main alternative, JuliaCall, embeds Julia in the R process. Both work for simple cases. With JuliaCall, though, R and Julia share memory, threads, signal handlers and the dynamic linker. A problem in any of them crashes both. We have seen segfaults from LD_LIBRARY_PATH poisoning of subprocess Julia, from Pkg.activate(<path>) followed by Pkg.instantiate() in in-process JuliaCall, and from accessing fields of NamedTuple results via julia_eval. With JuliaConnectoR, a Julia segfault closes a TCP socket while R keeps running. A crashed simulation can then be recovered. Out-of-process Julia also leaves a much shorter list of things you must avoid.

JuliaCall has a larger ecosystem and more frequent commits, but most of that activity is platform-compatibility work. Its in-process linking problems cannot be fixed without changing its architecture.

Consumer packages call juliabridge::eval_julia(), call_julia() and import_julia() and never touch JuliaConnectoR directly. The choice of backend stays in one place.

Installation

# install.packages("remotes")
# remotes::install_github("epiforecasts/juliabridge")
install.packages("juliabridge", repos = c('https://epiforecasts.r-universe.dev', 'https://cloud.r-project.org'))

You also need Julia installed; juliaup is recommended.

Usage in a consuming R package

# In your package's R/zzz.R (or similar):

.mypkg_env <- new.env(parent = emptyenv())

#' @export
setup_mypkg <- function(install = TRUE) {
  juliabridge::julia_ready(
    packages  = c("EpiBranch", "Distributions", "Random"),
    github    = c(EpiBranch = "epiforecasts/EpiBranch.jl"),
    state_env = .mypkg_env,
    install   = install
  )
  juliabridge::julia_load_bridge(
    package = "mypkg",
    files   = c("dist_lookup.jl", "simulate.jl")
  )
}

.ensure_julia <- function() {
  juliabridge::ensure_julia(.mypkg_env, setup_mypkg)
}

Then in any function that touches Julia:

my_function <- function(x) {
  .ensure_julia()
  juliabridge::call_julia("MyJuliaPkg.do_something", x)
}

API

Out of scope

Status

Used in ringbpjl; migrations of forecastbaselines, EpiAwareR, and epinow2julia are in flight.

License

MIT