class: center, middle, inverse, title-slide # Using Rust code in R packages ##
### Jeroen Ooms ### 2018/05/15 --- class: center middle # More Information ## https://github.com/r-rust --- # First: Tradition -- ![ijsco2](ijsco2.jpg) --- class: inverse, center, middle # What is Rust? --- # What is Rust? New system programming language, alternative to C / C++: ```rust fn main() { let greetings = ["Hello", "Hola", "Bonjour", "Ciao", "こんにちは", "안녕하세요", "Cześć", "Olá", "Здравствуйте", "Chào bạn", "您好", "Hallo", "Hej", "Ahoj", "سلام"]; for (num, greeting) in greetings.iter().enumerate() { print!("{} World!\n", greeting); } } ``` Compile with `rustc` toolchain: ```sh rustc hello.rs ./hello ``` --- # How to install Rust? Homebrew: ```sh brew install rust ``` Debian/Ubuntu: ```sh sudo apt-get install cargo ``` Fedora / CentOS: ```sh sudo yum install cargo ``` Windows: download `rustup` from https://win.rustup.rs and run: ```sh rustup-init.exe -y --default-host x86_64-pc-windows-gnu rustup target add i686-pc-windows-gnu ``` --- # OK, but why yet another language? Technical benefits of Rust over C/C++: - Rust is __memory safe__. By design, Rust code can't have dangling pointers, buffer overflows or other type of memory related errors. - Rust is as __fast__ as C/C++ while it is far safer. - Rust is great at __concurrent__ programming. - Native __cross__ compiling to many architectures. -- But we won't talk about this today. The main selling point for us is: -- - Native package manager: __cargo__ - Rust needs no runtime library. -- Unlike C/C++, Rust includes a package manager and repository for installing and publishing libraries. -- Even better: `cargo` works __seamlessly__ with R, even on Windows! --- # What is Cargo ? The rustc package manager `cargo` and builds directly from [crates.io](https://crates.io)! -- For example, we want to install the [gifski](https://crates.io/crates/gifski) crate: ![website.png](website.png) --- ![installer](installer.gif) --- # What is Cargo ? This package has 40+ recursive dependencies. Cargo downloads, builds, links, and optimizes everything automatically. ```sh cargo install gifski ``` ```sh # all done! gifski --help ``` __Note 1: *No autoconf, cmake, pkg-config, linking errors, managing dependencies, etc*__. __Note 2: *Versioned Dependencies FTW!*__ This is a very strong foundation for building stuff. --- class: center middle # Minimal Example ## https://github.com/r-rust/hellorust Linux: [![Linux](https://travis-ci.org/r-rust/hellorust.svg)](https://travis-ci.org/r-rust/hellorust) | MacOS: [![MacOS](https://travis-ci.org/r-rust/hellorust.svg)](https://travis-ci.org/r-rust/hellorust) | Windows: [![Windows](https://ci.appveyor.com/api/projects/status/github/r-rust/hellorust)](https://ci.appveyor.com/project/jeroen/hellorust) --- # R Package: hellorust Package structure (from the hellorust [readme](https://github.com/r-rust/hellorust#package-structure)): ```txt hellorust ├─ configure ← checks if 'cargo' is installed ├─ src │ ├─ myrustlib ← bundled cargo package with your code │ | ├─ Cargo.toml ← cargo dependencies and metadata │ | ├─ src ← rust source code │ | └─ api.h ← C headers for exported rust API | | │ ├─ Makevars ← Ties everything together │ └─ wrapper.c ← C code for R package ├─ DESCRIPTION └─ R ← Standard R+C stuff ``` -- __Note the R package includes a little cargo package `myrustlib` which depends on the cargo crates we actually want to call from R.__ --- # R Package: hellorust The `Cargo.toml` file from hellorust looks like this: ```toml [package] name = "myrustlib" version = "0.1.0" authors = ["Jeroen <jeroenooms@gmail.com>"] [lib] crate-type = ["staticlib"] [dependencies] rand = "0.4.1" libc = "*" ``` This creates a static library including all dependencies, this can be linked into the R package using the regular `PKG_LIBS` in `Makevars`. --- # R Package: hellorust Your rust source code (e.g wrappers) goes in [/src/myrustlib/src](https://github.com/r-rust/hellorust/tree/master/src/myrustlib/src). The "main" library file is always [lib.rs](https://github.com/r-rust/hellorust/blob/master/src/myrustlib/src/lib.rs) and looks like this: ```rust // Import dependencies extern crate libc; extern crate rand; // Modules are other .rs source files mod hello; mod random; mod mythreads; // Export functions called by R pub use hello::string_from_rust; pub use random::random_number; pub use mythreads::run_threads; ``` --- # R Package: hellorust The R `Makevars` file for this package triggers cargo build: ```make LIBDIR = myrustlib/target/release STATLIB = $(LIBDIR)/libmyrustlib.a PKG_LIBS = -L$(LIBDIR) -lmyrustlib -lresolv $(SHLIB): $(STATLIB) $(STATLIB): cargo build --release --manifest-path=myrustlib/Cargo.toml clean: rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS) myrustlib/target ``` This follows standard R rules (SHLIB is the package dll). --- ![demovid](hellorust.gif) --- class: center, middle # Rust has no Runtime !! ### The binary package does not depend on Rust or Cargo! --- class: inverse center middle # Writing Wrappers ### You can call compiled Rust code directly from C by defining the Rust function signature as a C header. --- # R Package: hellorust: returning an integer ```rust # Contents of random.rs use libc::int32_t; use rand::random; #[no_mangle] pub extern fn random_number() -> int32_t { let tuple = random::<i32>(); tuple as int32_t } ``` C header for this function: ```c extern int32_t random_number(); ``` Call from R: ```r > hellorust::random() [1] -128740839 ``` --- # R Package: hellorust: returning a string ```rust use std::ffi::CString; use std::os::raw::c_char; #[no_mangle] pub extern fn string_from_rust() -> *const c_char { let s = CString::new("Hello ピカチュウ !").unwrap(); let p = s.as_ptr(); std::mem::forget(s); p } ``` C header for this function: ```c extern char * string_from_rust(); ``` Call from R: ```r > hellorust::hello() [1] "Hello ピカチュウ !" ``` --- # R Package: hellorust: example with threads ```rust use std::thread; use std::time::Duration; #[no_mangle] pub extern fn run_threads() { thread::spawn(|| { for i in 1..10 { println!("hi number {} from the spawned thread!", i); thread::sleep(Duration::from_millis(1)); } }); for i in 1..5 { println!("hi number {} from the main thread!", i); thread::sleep(Duration::from_millis(1)); } } ``` Can you guess the output? ```r > hellorust::runthreads() ``` --- # R package: gifski Try installing the R wrapper. ```r # Windows users need the latest build of Rtools 3.5! devtool::install_github("r-rust/gifski") ``` -- The R package [gifski](https://github.com/r-rust/gifski) contains __no rust code__! It calls [gifski](https://github.com/ImageOptim/gifski) directly using the [gifski.h](https://github.com/ImageOptim/gifski/blob/master/gifski.h) header (provided by the author) which defines the api structs and function signatures in C form. -- This allows us to call the compiled rust gifski library directly from C without any "bridging" frameworks. --- # Call R from Rust? All current R packages call Rust functions via C using C headers and structs. Very simple and powerful. -- There is also an (inactive?) project called [RustinR](https://github.com/rustr/rustinr) which has an extensive Rcpp-like framework for calling the R api from within Rust code. ```rust #[no_mangle] pub extern "C" fn rustr_say_hi(a : SEXP)->SEXP{ let a_ : String = unwrapr!( String::rnew(a) ); let res = say_hi(a_); let res_sexp : SEXP = unwrapr!(res.intor()); return res_sexp; } ``` -- If you're interested in wrapping Cargo crates, this may be overkill. But if you want to create and manipulate R (SEXP) objects in Rust it may be useful. --- class: center middle # Thanks! ## https://github.com/r-rust Slides created via the R package [**xaringan**](https://github.com/yihui/xaringan). The chakra comes from [remark.js](https://remarkjs.com), [**knitr**](http://yihui.name/knitr), and [R Markdown](https://rmarkdown.rstudio.com).