Repository files navigation

Minimal Example of Calling Rust from R

R build statusLicense: MIT

This is a template package to demonstrate how to call Rust from R using the extendr-api crate, based on the helloextendr package.

Installation

Before you can develop with this package, you need to install a working Rust toolchain. It's probably best to use rustup.

On Windows, you'll also have to add the i686-pc-windows-gnu and x86_64-pc-windows-gnu targets:

rustup target add x86_64-pc-windows-gnu
rustup target add i686-pc-windows-gnu

Since this project is intended to provide reference implementations, you will probably find more utility in cloning the repository than installing the package. If you insist, you can install rustbind with:

devtools::install_github("ericwburden/rustbind")

Development

To build this package, and generate the Rust function wrappers in R/extendr-wrappers.R, you should prefer to source the build.R build script over the 'Clean and Rebuild' menu option in RStudio.

Usage

In order to demonstrate the process of adding a simple Rust function, callable by R, via the extendr_api crate, consider the Bubble Sort algorithm. Briefly, Bubble Sorting requires traversing a list of elements and exchanging them pair-wise until the entire list is in order, as demonstrated in the below pseudocode.

function bubble_sort(arr) {
n = len(arr) // Traverse through all array elements for i in range from 0 to n {
for j in range from 0 to (n-i-1) { // For each element `j`, swap with element `j+1` if element `j` is
// larger
if arr[j] > arr[j+1] : swap(arr[j], arr[j+1])
}
}
}

This is not an efficient way to sort a list (or vector), but it is an algorithm that can be run much faster by implementing it in Rust instead of R alone.

Adding the Rust Implementation

To keep the code nicely separated, start by creating a file rustbind/src/rust/src/bubble_sort.rs with the following code:

use extendr_api::prelude::{Real,RobjItertools};pub(crate)fnbubble_sort_fn(input:Real) -> Real{letmut nvec:Vec<_> = input.collect();let len = nvec.len();for idx in0..(len - 1){let last_idx = len - idx - 1;for inner_idx in0..last_idx {if nvec[inner_idx] > nvec[inner_idx + 1]{
nvec.swap(inner_idx, inner_idx + 1)}}}
nvec.iter().collect_robj().as_real_iter().unwrap()}

In order to generate the wrappers and bindings, add the following to rustbind/src/rust/src/lib.rs:

use extendr_api::prelude::*;mod bubble_sort;// Other modules here.../// Bubble Sort a vector of doubles/// /// Demonstrates using Rust to perform a Bubble Sort on a vector of doubles/// /// @params input A double vector to sort/// @return a sorted vector of doubles/// /// @examples bubble_sort(runif(1000))/// /// @export#[extendr]fnbubble_sort(input:Real) -> Real{
bubble_sort::bubble_sort_fn(input)}extendr_module!{mod rustbind;// Other functions to export go here...fn bubble_sort;}

And that's it! After sourcing build.R in order to build, install, and load your package, you will be able to call yourpackagename::bubble_sort(n) and take advantage of Rust's blazing speed in your R code. Also, note the format of that doc comment. If you've written R packages before, you'll recognize those as roxygen2 comments, which are used to build the documentation for your package, as well as control whether or not the function is available outside your package (via package_name::function_name) through the '@export' tag.

Was it Worth It?

Now, that's definitely a relatively more complicated way to just write Rust code instead of R code, was it worth it? I would argue that for any type of exploratory or ad hoc analysis, the answer is most definitely no. But, if you're writing R code that will be used in production to run the same type of algorithm over and over again, the speed gains are tremendous. Consider the analogous R implementation of the Bubble Sort algorithm:

#' Bubble Sorting - R Implementation#'#' @param nums numeric vector to sort#' @return a sorted integer vector#' @exportbubble_sort_r<-function(nums) {
nums<-if (missing(nums)) { stats::runif(1000) }
n<- length(nums)
for (iin1:(n-1)) {
for (jin1:(n-i)) {
if (nums[j] >nums[j+1]) {
temp<-nums[j]
nums[j] <-nums[j+1]
nums[j+1] <-temp
}
}
}
nums
}

Notwithstanding the slight modification needed because R doesn't provide a convenient way to swap values without using a temporary variable, this is nearly identical to our Rust implementation. But, if we benchmark the two functions...

 test replications elapsed relative user.self sys.self user.child sys.child
2 bubble_sort_r(input) 100 384.256 24.789 384.045 0 0.022 0.123
1 bubble_sort(input) 100 15.501 1.000 15.502 0 0.000 0.000

We see that, for vectors of 10k random numbers, the Rust implementation is nearly 25x faster than the R implementation, for the same task. That's a pretty massive speedup, considering we've used a fairly naive implementation of this algorithm (we're copying the vector at least twice). So, there will definitely be situations where you will save a huge amount of processing time by implementing functions in Rust (just like if you were implementing an underlying function in C or C++), with the added safety guarantees of Rust. You may find you are even able to perform calculations that simply aren't feasible (at least not in any reasonable amount of time) in pure R. So, happy coding!

Extending extendr

You may encounter situations in which extendr does not behave as expected or support your use case (yet). As of 2021-03-28, extendr v0.2.0 doesn't support (so far as I can tell) passing in character vectors that may contain NA's or correctly giving back integer vectors with NA's (they get converted to 0). There are at least two different ways to address these issues as they arise:

Wrapping Rust Calls in R

One strategy is to 'wrap' the functions automatically generated by extendr to address these issues by modifying the input or output on the R side. For an example of this, see R/r-wrappers.R.

Implementing R <-> Rust Conversions in Rust

Another strategy is to create structs and types in Rust with appropriate trait implementations (particularly FromRobj and From<T> for Robj) in your Rust module. This has the added benefit of plugging directly into the extendr infrastructure. For an example of this, see src/rust/src/structs/char_vec.rs.

About

Call Rust functions from R using the extendr-api crate

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Minimal Example of Calling Rust from R

R build statusLicense: MIT

This is a template package to demonstrate how to call Rust from R using the extendr-api crate, based on the helloextendr package.

Installation

Before you can develop with this package, you need to install a working Rust toolchain. It's probably best to use rustup.

On Windows, you'll also have to add the i686-pc-windows-gnu and x86_64-pc-windows-gnu targets:

rustup target add x86_64-pc-windows-gnu
rustup target add i686-pc-windows-gnu

Since this project is intended to provide reference implementations, you will probably find more utility in cloning the repository than installing the package. If you insist, you can install rustbind with:

devtools::install_github("ericwburden/rustbind")

Development

To build this package, and generate the Rust function wrappers in R/extendr-wrappers.R, you should prefer to source the build.R build script over the 'Clean and Rebuild' menu option in RStudio.

Usage

In order to demonstrate the process of adding a simple Rust function, callable by R, via the extendr_api crate, consider the Bubble Sort algorithm. Briefly, Bubble Sorting requires traversing a list of elements and exchanging them pair-wise until the entire list is in order, as demonstrated in the below pseudocode.

function bubble_sort(arr) {
n = len(arr) // Traverse through all array elements for i in range from 0 to n {
for j in range from 0 to (n-i-1) { // For each element `j`, swap with element `j+1` if element `j` is
// larger
if arr[j] > arr[j+1] : swap(arr[j], arr[j+1])
}
}
}

This is not an efficient way to sort a list (or vector), but it is an algorithm that can be run much faster by implementing it in Rust instead of R alone.

Adding the Rust Implementation

To keep the code nicely separated, start by creating a file rustbind/src/rust/src/bubble_sort.rs with the following code:

use extendr_api::prelude::{Real,RobjItertools};pub(crate)fnbubble_sort_fn(input:Real) -> Real{letmut nvec:Vec<_> = input.collect();let len = nvec.len();for idx in0..(len - 1){let last_idx = len - idx - 1;for inner_idx in0..last_idx {if nvec[inner_idx] > nvec[inner_idx + 1]{
nvec.swap(inner_idx, inner_idx + 1)}}}
nvec.iter().collect_robj().as_real_iter().unwrap()}

In order to generate the wrappers and bindings, add the following to rustbind/src/rust/src/lib.rs:

use extendr_api::prelude::*;mod bubble_sort;// Other modules here.../// Bubble Sort a vector of doubles/// /// Demonstrates using Rust to perform a Bubble Sort on a vector of doubles/// /// @params input A double vector to sort/// @return a sorted vector of doubles/// /// @examples bubble_sort(runif(1000))/// /// @export#[extendr]fnbubble_sort(input:Real) -> Real{
bubble_sort::bubble_sort_fn(input)}extendr_module!{mod rustbind;// Other functions to export go here...fn bubble_sort;}

And that's it! After sourcing build.R in order to build, install, and load your package, you will be able to call yourpackagename::bubble_sort(n) and take advantage of Rust's blazing speed in your R code. Also, note the format of that doc comment. If you've written R packages before, you'll recognize those as roxygen2 comments, which are used to build the documentation for your package, as well as control whether or not the function is available outside your package (via package_name::function_name) through the '@export' tag.

Was it Worth It?

Now, that's definitely a relatively more complicated way to just write Rust code instead of R code, was it worth it? I would argue that for any type of exploratory or ad hoc analysis, the answer is most definitely no. But, if you're writing R code that will be used in production to run the same type of algorithm over and over again, the speed gains are tremendous. Consider the analogous R implementation of the Bubble Sort algorithm:

#' Bubble Sorting - R Implementation#'#' @param nums numeric vector to sort#' @return a sorted integer vector#' @exportbubble_sort_r<-function(nums) {
nums<-if (missing(nums)) { stats::runif(1000) }
n<- length(nums)
for (iin1:(n-1)) {
for (jin1:(n-i)) {
if (nums[j] >nums[j+1]) {
temp<-nums[j]
nums[j] <-nums[j+1]
nums[j+1] <-temp
}
}
}
nums
}

Notwithstanding the slight modification needed because R doesn't provide a convenient way to swap values without using a temporary variable, this is nearly identical to our Rust implementation. But, if we benchmark the two functions...

 test replications elapsed relative user.self sys.self user.child sys.child
2 bubble_sort_r(input) 100 384.256 24.789 384.045 0 0.022 0.123
1 bubble_sort(input) 100 15.501 1.000 15.502 0 0.000 0.000

We see that, for vectors of 10k random numbers, the Rust implementation is nearly 25x faster than the R implementation, for the same task. That's a pretty massive speedup, considering we've used a fairly naive implementation of this algorithm (we're copying the vector at least twice). So, there will definitely be situations where you will save a huge amount of processing time by implementing functions in Rust (just like if you were implementing an underlying function in C or C++), with the added safety guarantees of Rust. You may find you are even able to perform calculations that simply aren't feasible (at least not in any reasonable amount of time) in pure R. So, happy coding!

Extending extendr

You may encounter situations in which extendr does not behave as expected or support your use case (yet). As of 2021-03-28, extendr v0.2.0 doesn't support (so far as I can tell) passing in character vectors that may contain NA's or correctly giving back integer vectors with NA's (they get converted to 0). There are at least two different ways to address these issues as they arise:

Wrapping Rust Calls in R

One strategy is to 'wrap' the functions automatically generated by extendr to address these issues by modifying the input or output on the R side. For an example of this, see R/r-wrappers.R.

Implementing R <-> Rust Conversions in Rust

Another strategy is to create structs and types in Rust with appropriate trait implementations (particularly FromRobj and From<T> for Robj) in your Rust module. This has the added benefit of plugging directly into the extendr infrastructure. For an example of this, see src/rust/src/structs/char_vec.rs.

About

Call Rust functions from R using the extendr-api crate

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Minimal Example of Calling Rust from R

R build statusLicense: MIT

This is a template package to demonstrate how to call Rust from R using the extendr-api crate, based on the helloextendr package.

Installation

Before you can develop with this package, you need to install a working Rust toolchain. It's probably best to use rustup.

On Windows, you'll also have to add the i686-pc-windows-gnu and x86_64-pc-windows-gnu targets:

rustup target add x86_64-pc-windows-gnu
rustup target add i686-pc-windows-gnu

Since this project is intended to provide reference implementations, you will probably find more utility in cloning the repository than installing the package. If you insist, you can install rustbind with:

devtools::install_github("ericwburden/rustbind")

Development

To build this package, and generate the Rust function wrappers in R/extendr-wrappers.R, you should prefer to source the build.R build script over the 'Clean and Rebuild' menu option in RStudio.

Usage

In order to demonstrate the process of adding a simple Rust function, callable by R, via the extendr_api crate, consider the Bubble Sort algorithm. Briefly, Bubble Sorting requires traversing a list of elements and exchanging them pair-wise until the entire list is in order, as demonstrated in the below pseudocode.

function bubble_sort(arr) {
n = len(arr) // Traverse through all array elements for i in range from 0 to n {
for j in range from 0 to (n-i-1) { // For each element `j`, swap with element `j+1` if element `j` is
// larger
if arr[j] > arr[j+1] : swap(arr[j], arr[j+1])
}
}
}

This is not an efficient way to sort a list (or vector), but it is an algorithm that can be run much faster by implementing it in Rust instead of R alone.

Adding the Rust Implementation

To keep the code nicely separated, start by creating a file rustbind/src/rust/src/bubble_sort.rs with the following code:

use extendr_api::prelude::{Real,RobjItertools};pub(crate)fnbubble_sort_fn(input:Real) -> Real{letmut nvec:Vec<_> = input.collect();let len = nvec.len();for idx in0..(len - 1){let last_idx = len - idx - 1;for inner_idx in0..last_idx {if nvec[inner_idx] > nvec[inner_idx + 1]{
nvec.swap(inner_idx, inner_idx + 1)}}}
nvec.iter().collect_robj().as_real_iter().unwrap()}

In order to generate the wrappers and bindings, add the following to rustbind/src/rust/src/lib.rs:

use extendr_api::prelude::*;mod bubble_sort;// Other modules here.../// Bubble Sort a vector of doubles/// /// Demonstrates using Rust to perform a Bubble Sort on a vector of doubles/// /// @params input A double vector to sort/// @return a sorted vector of doubles/// /// @examples bubble_sort(runif(1000))/// /// @export#[extendr]fnbubble_sort(input:Real) -> Real{
bubble_sort::bubble_sort_fn(input)}extendr_module!{mod rustbind;// Other functions to export go here...fn bubble_sort;}

And that's it! After sourcing build.R in order to build, install, and load your package, you will be able to call yourpackagename::bubble_sort(n) and take advantage of Rust's blazing speed in your R code. Also, note the format of that doc comment. If you've written R packages before, you'll recognize those as roxygen2 comments, which are used to build the documentation for your package, as well as control whether or not the function is available outside your package (via package_name::function_name) through the '@export' tag.

Was it Worth It?

Now, that's definitely a relatively more complicated way to just write Rust code instead of R code, was it worth it? I would argue that for any type of exploratory or ad hoc analysis, the answer is most definitely no. But, if you're writing R code that will be used in production to run the same type of algorithm over and over again, the speed gains are tremendous. Consider the analogous R implementation of the Bubble Sort algorithm:

#' Bubble Sorting - R Implementation#'#' @param nums numeric vector to sort#' @return a sorted integer vector#' @exportbubble_sort_r<-function(nums) {
nums<-if (missing(nums)) { stats::runif(1000) }
n<- length(nums)
for (iin1:(n-1)) {
for (jin1:(n-i)) {
if (nums[j] >nums[j+1]) {
temp<-nums[j]
nums[j] <-nums[j+1]
nums[j+1] <-temp
}
}
}
nums
}

Notwithstanding the slight modification needed because R doesn't provide a convenient way to swap values without using a temporary variable, this is nearly identical to our Rust implementation. But, if we benchmark the two functions...

 test replications elapsed relative user.self sys.self user.child sys.child
2 bubble_sort_r(input) 100 384.256 24.789 384.045 0 0.022 0.123
1 bubble_sort(input) 100 15.501 1.000 15.502 0 0.000 0.000

We see that, for vectors of 10k random numbers, the Rust implementation is nearly 25x faster than the R implementation, for the same task. That's a pretty massive speedup, considering we've used a fairly naive implementation of this algorithm (we're copying the vector at least twice). So, there will definitely be situations where you will save a huge amount of processing time by implementing functions in Rust (just like if you were implementing an underlying function in C or C++), with the added safety guarantees of Rust. You may find you are even able to perform calculations that simply aren't feasible (at least not in any reasonable amount of time) in pure R. So, happy coding!

Extending extendr

You may encounter situations in which extendr does not behave as expected or support your use case (yet). As of 2021-03-28, extendr v0.2.0 doesn't support (so far as I can tell) passing in character vectors that may contain NA's or correctly giving back integer vectors with NA's (they get converted to 0). There are at least two different ways to address these issues as they arise:

Wrapping Rust Calls in R

One strategy is to 'wrap' the functions automatically generated by extendr to address these issues by modifying the input or output on the R side. For an example of this, see R/r-wrappers.R.

Implementing R <-> Rust Conversions in Rust

Another strategy is to create structs and types in Rust with appropriate trait implementations (particularly FromRobj and From<T> for Robj) in your Rust module. This has the added benefit of plugging directly into the extendr infrastructure. For an example of this, see src/rust/src/structs/char_vec.rs.

About

Call Rust functions from R using the extendr-api crate

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Minimal Example of Calling Rust from R

R build statusLicense: MIT

This is a template package to demonstrate how to call Rust from R using the extendr-api crate, based on the helloextendr package.

Installation

Before you can develop with this package, you need to install a working Rust toolchain. It's probably best to use rustup.

On Windows, you'll also have to add the i686-pc-windows-gnu and x86_64-pc-windows-gnu targets:

rustup target add x86_64-pc-windows-gnu
rustup target add i686-pc-windows-gnu

Since this project is intended to provide reference implementations, you will probably find more utility in cloning the repository than installing the package. If you insist, you can install rustbind with:

devtools::install_github("ericwburden/rustbind")

Development

To build this package, and generate the Rust function wrappers in R/extendr-wrappers.R, you should prefer to source the build.R build script over the 'Clean and Rebuild' menu option in RStudio.

Usage

In order to demonstrate the process of adding a simple Rust function, callable by R, via the extendr_api crate, consider the Bubble Sort algorithm. Briefly, Bubble Sorting requires traversing a list of elements and exchanging them pair-wise until the entire list is in order, as demonstrated in the below pseudocode.

function bubble_sort(arr) {
n = len(arr) // Traverse through all array elements for i in range from 0 to n {
for j in range from 0 to (n-i-1) { // For each element `j`, swap with element `j+1` if element `j` is
// larger
if arr[j] > arr[j+1] : swap(arr[j], arr[j+1])
}
}
}

This is not an efficient way to sort a list (or vector), but it is an algorithm that can be run much faster by implementing it in Rust instead of R alone.

Adding the Rust Implementation

To keep the code nicely separated, start by creating a file rustbind/src/rust/src/bubble_sort.rs with the following code:

use extendr_api::prelude::{Real,RobjItertools};pub(crate)fnbubble_sort_fn(input:Real) -> Real{letmut nvec:Vec<_> = input.collect();let len = nvec.len();for idx in0..(len - 1){let last_idx = len - idx - 1;for inner_idx in0..last_idx {if nvec[inner_idx] > nvec[inner_idx + 1]{
nvec.swap(inner_idx, inner_idx + 1)}}}
nvec.iter().collect_robj().as_real_iter().unwrap()}

In order to generate the wrappers and bindings, add the following to rustbind/src/rust/src/lib.rs:

use extendr_api::prelude::*;mod bubble_sort;// Other modules here.../// Bubble Sort a vector of doubles/// /// Demonstrates using Rust to perform a Bubble Sort on a vector of doubles/// /// @params input A double vector to sort/// @return a sorted vector of doubles/// /// @examples bubble_sort(runif(1000))/// /// @export#[extendr]fnbubble_sort(input:Real) -> Real{
bubble_sort::bubble_sort_fn(input)}extendr_module!{mod rustbind;// Other functions to export go here...fn bubble_sort;}

And that's it! After sourcing build.R in order to build, install, and load your package, you will be able to call yourpackagename::bubble_sort(n) and take advantage of Rust's blazing speed in your R code. Also, note the format of that doc comment. If you've written R packages before, you'll recognize those as roxygen2 comments, which are used to build the documentation for your package, as well as control whether or not the function is available outside your package (via package_name::function_name) through the '@export' tag.

Was it Worth It?

Now, that's definitely a relatively more complicated way to just write Rust code instead of R code, was it worth it? I would argue that for any type of exploratory or ad hoc analysis, the answer is most definitely no. But, if you're writing R code that will be used in production to run the same type of algorithm over and over again, the speed gains are tremendous. Consider the analogous R implementation of the Bubble Sort algorithm:

#' Bubble Sorting - R Implementation#'#' @param nums numeric vector to sort#' @return a sorted integer vector#' @exportbubble_sort_r<-function(nums) {
nums<-if (missing(nums)) { stats::runif(1000) }
n<- length(nums)
for (iin1:(n-1)) {
for (jin1:(n-i)) {
if (nums[j] >nums[j+1]) {
temp<-nums[j]
nums[j] <-nums[j+1]
nums[j+1] <-temp
}
}
}
nums
}

Notwithstanding the slight modification needed because R doesn't provide a convenient way to swap values without using a temporary variable, this is nearly identical to our Rust implementation. But, if we benchmark the two functions...

 test replications elapsed relative user.self sys.self user.child sys.child
2 bubble_sort_r(input) 100 384.256 24.789 384.045 0 0.022 0.123
1 bubble_sort(input) 100 15.501 1.000 15.502 0 0.000 0.000

We see that, for vectors of 10k random numbers, the Rust implementation is nearly 25x faster than the R implementation, for the same task. That's a pretty massive speedup, considering we've used a fairly naive implementation of this algorithm (we're copying the vector at least twice). So, there will definitely be situations where you will save a huge amount of processing time by implementing functions in Rust (just like if you were implementing an underlying function in C or C++), with the added safety guarantees of Rust. You may find you are even able to perform calculations that simply aren't feasible (at least not in any reasonable amount of time) in pure R. So, happy coding!

Extending extendr

You may encounter situations in which extendr does not behave as expected or support your use case (yet). As of 2021-03-28, extendr v0.2.0 doesn't support (so far as I can tell) passing in character vectors that may contain NA's or correctly giving back integer vectors with NA's (they get converted to 0). There are at least two different ways to address these issues as they arise:

Wrapping Rust Calls in R

One strategy is to 'wrap' the functions automatically generated by extendr to address these issues by modifying the input or output on the R side. For an example of this, see R/r-wrappers.R.

Implementing R <-> Rust Conversions in Rust

Another strategy is to create structs and types in Rust with appropriate trait implementations (particularly FromRobj and From<T> for Robj) in your Rust module. This has the added benefit of plugging directly into the extendr infrastructure. For an example of this, see src/rust/src/structs/char_vec.rs.

About

Call Rust functions from R using the extendr-api crate

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Minimal Example of Calling Rust from R

R build statusLicense: MIT

This is a template package to demonstrate how to call Rust from R using the extendr-api crate, based on the helloextendr package.

Installation

Before you can develop with this package, you need to install a working Rust toolchain. It's probably best to use rustup.

On Windows, you'll also have to add the i686-pc-windows-gnu and x86_64-pc-windows-gnu targets:

rustup target add x86_64-pc-windows-gnu
rustup target add i686-pc-windows-gnu

Since this project is intended to provide reference implementations, you will probably find more utility in cloning the repository than installing the package. If you insist, you can install rustbind with:

devtools::install_github("ericwburden/rustbind")

Development

To build this package, and generate the Rust function wrappers in R/extendr-wrappers.R, you should prefer to source the build.R build script over the 'Clean and Rebuild' menu option in RStudio.

Usage

In order to demonstrate the process of adding a simple Rust function, callable by R, via the extendr_api crate, consider the Bubble Sort algorithm. Briefly, Bubble Sorting requires traversing a list of elements and exchanging them pair-wise until the entire list is in order, as demonstrated in the below pseudocode.

function bubble_sort(arr) {
n = len(arr) // Traverse through all array elements for i in range from 0 to n {
for j in range from 0 to (n-i-1) { // For each element `j`, swap with element `j+1` if element `j` is
// larger
if arr[j] > arr[j+1] : swap(arr[j], arr[j+1])
}
}
}

This is not an efficient way to sort a list (or vector), but it is an algorithm that can be run much faster by implementing it in Rust instead of R alone.

Adding the Rust Implementation

To keep the code nicely separated, start by creating a file rustbind/src/rust/src/bubble_sort.rs with the following code:

use extendr_api::prelude::{Real,RobjItertools};pub(crate)fnbubble_sort_fn(input:Real) -> Real{letmut nvec:Vec<_> = input.collect();let len = nvec.len();for idx in0..(len - 1){let last_idx = len - idx - 1;for inner_idx in0..last_idx {if nvec[inner_idx] > nvec[inner_idx + 1]{
nvec.swap(inner_idx, inner_idx + 1)}}}
nvec.iter().collect_robj().as_real_iter().unwrap()}

In order to generate the wrappers and bindings, add the following to rustbind/src/rust/src/lib.rs:

use extendr_api::prelude::*;mod bubble_sort;// Other modules here.../// Bubble Sort a vector of doubles/// /// Demonstrates using Rust to perform a Bubble Sort on a vector of doubles/// /// @params input A double vector to sort/// @return a sorted vector of doubles/// /// @examples bubble_sort(runif(1000))/// /// @export#[extendr]fnbubble_sort(input:Real) -> Real{
bubble_sort::bubble_sort_fn(input)}extendr_module!{mod rustbind;// Other functions to export go here...fn bubble_sort;}

And that's it! After sourcing build.R in order to build, install, and load your package, you will be able to call yourpackagename::bubble_sort(n) and take advantage of Rust's blazing speed in your R code. Also, note the format of that doc comment. If you've written R packages before, you'll recognize those as roxygen2 comments, which are used to build the documentation for your package, as well as control whether or not the function is available outside your package (via package_name::function_name) through the '@export' tag.

Was it Worth It?

Now, that's definitely a relatively more complicated way to just write Rust code instead of R code, was it worth it? I would argue that for any type of exploratory or ad hoc analysis, the answer is most definitely no. But, if you're writing R code that will be used in production to run the same type of algorithm over and over again, the speed gains are tremendous. Consider the analogous R implementation of the Bubble Sort algorithm:

#' Bubble Sorting - R Implementation#'#' @param nums numeric vector to sort#' @return a sorted integer vector#' @exportbubble_sort_r<-function(nums) {
nums<-if (missing(nums)) { stats::runif(1000) }
n<- length(nums)
for (iin1:(n-1)) {
for (jin1:(n-i)) {
if (nums[j] >nums[j+1]) {
temp<-nums[j]
nums[j] <-nums[j+1]
nums[j+1] <-temp
}
}
}
nums
}

Notwithstanding the slight modification needed because R doesn't provide a convenient way to swap values without using a temporary variable, this is nearly identical to our Rust implementation. But, if we benchmark the two functions...

 test replications elapsed relative user.self sys.self user.child sys.child
2 bubble_sort_r(input) 100 384.256 24.789 384.045 0 0.022 0.123
1 bubble_sort(input) 100 15.501 1.000 15.502 0 0.000 0.000

We see that, for vectors of 10k random numbers, the Rust implementation is nearly 25x faster than the R implementation, for the same task. That's a pretty massive speedup, considering we've used a fairly naive implementation of this algorithm (we're copying the vector at least twice). So, there will definitely be situations where you will save a huge amount of processing time by implementing functions in Rust (just like if you were implementing an underlying function in C or C++), with the added safety guarantees of Rust. You may find you are even able to perform calculations that simply aren't feasible (at least not in any reasonable amount of time) in pure R. So, happy coding!

Extending extendr

You may encounter situations in which extendr does not behave as expected or support your use case (yet). As of 2021-03-28, extendr v0.2.0 doesn't support (so far as I can tell) passing in character vectors that may contain NA's or correctly giving back integer vectors with NA's (they get converted to 0). There are at least two different ways to address these issues as they arise:

Wrapping Rust Calls in R

One strategy is to 'wrap' the functions automatically generated by extendr to address these issues by modifying the input or output on the R side. For an example of this, see R/r-wrappers.R.

Implementing R <-> Rust Conversions in Rust

Another strategy is to create structs and types in Rust with appropriate trait implementations (particularly FromRobj and From<T> for Robj) in your Rust module. This has the added benefit of plugging directly into the extendr infrastructure. For an example of this, see src/rust/src/structs/char_vec.rs.

About

Call Rust functions from R using the extendr-api crate

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Minimal Example of Calling Rust from R

R build statusLicense: MIT

This is a template package to demonstrate how to call Rust from R using the extendr-api crate, based on the helloextendr package.

Installation

Before you can develop with this package, you need to install a working Rust toolchain. It's probably best to use rustup.

On Windows, you'll also have to add the i686-pc-windows-gnu and x86_64-pc-windows-gnu targets:

rustup target add x86_64-pc-windows-gnu
rustup target add i686-pc-windows-gnu

Since this project is intended to provide reference implementations, you will probably find more utility in cloning the repository than installing the package. If you insist, you can install rustbind with:

devtools::install_github("ericwburden/rustbind")

Development

To build this package, and generate the Rust function wrappers in R/extendr-wrappers.R, you should prefer to source the build.R build script over the 'Clean and Rebuild' menu option in RStudio.

Usage

In order to demonstrate the process of adding a simple Rust function, callable by R, via the extendr_api crate, consider the Bubble Sort algorithm. Briefly, Bubble Sorting requires traversing a list of elements and exchanging them pair-wise until the entire list is in order, as demonstrated in the below pseudocode.

function bubble_sort(arr) {
n = len(arr) // Traverse through all array elements for i in range from 0 to n {
for j in range from 0 to (n-i-1) { // For each element `j`, swap with element `j+1` if element `j` is
// larger
if arr[j] > arr[j+1] : swap(arr[j], arr[j+1])
}
}
}

This is not an efficient way to sort a list (or vector), but it is an algorithm that can be run much faster by implementing it in Rust instead of R alone.

Adding the Rust Implementation

To keep the code nicely separated, start by creating a file rustbind/src/rust/src/bubble_sort.rs with the following code:

use extendr_api::prelude::{Real,RobjItertools};pub(crate)fnbubble_sort_fn(input:Real) -> Real{letmut nvec:Vec<_> = input.collect();let len = nvec.len();for idx in0..(len - 1){let last_idx = len - idx - 1;for inner_idx in0..last_idx {if nvec[inner_idx] > nvec[inner_idx + 1]{
nvec.swap(inner_idx, inner_idx + 1)}}}
nvec.iter().collect_robj().as_real_iter().unwrap()}

In order to generate the wrappers and bindings, add the following to rustbind/src/rust/src/lib.rs:

use extendr_api::prelude::*;mod bubble_sort;// Other modules here.../// Bubble Sort a vector of doubles/// /// Demonstrates using Rust to perform a Bubble Sort on a vector of doubles/// /// @params input A double vector to sort/// @return a sorted vector of doubles/// /// @examples bubble_sort(runif(1000))/// /// @export#[extendr]fnbubble_sort(input:Real) -> Real{
bubble_sort::bubble_sort_fn(input)}extendr_module!{mod rustbind;// Other functions to export go here...fn bubble_sort;}

And that's it! After sourcing build.R in order to build, install, and load your package, you will be able to call yourpackagename::bubble_sort(n) and take advantage of Rust's blazing speed in your R code. Also, note the format of that doc comment. If you've written R packages before, you'll recognize those as roxygen2 comments, which are used to build the documentation for your package, as well as control whether or not the function is available outside your package (via package_name::function_name) through the '@export' tag.

Was it Worth It?

Now, that's definitely a relatively more complicated way to just write Rust code instead of R code, was it worth it? I would argue that for any type of exploratory or ad hoc analysis, the answer is most definitely no. But, if you're writing R code that will be used in production to run the same type of algorithm over and over again, the speed gains are tremendous. Consider the analogous R implementation of the Bubble Sort algorithm:

#' Bubble Sorting - R Implementation#'#' @param nums numeric vector to sort#' @return a sorted integer vector#' @exportbubble_sort_r<-function(nums) {
nums<-if (missing(nums)) { stats::runif(1000) }
n<- length(nums)
for (iin1:(n-1)) {
for (jin1:(n-i)) {
if (nums[j] >nums[j+1]) {
temp<-nums[j]
nums[j] <-nums[j+1]
nums[j+1] <-temp
}
}
}
nums
}

Notwithstanding the slight modification needed because R doesn't provide a convenient way to swap values without using a temporary variable, this is nearly identical to our Rust implementation. But, if we benchmark the two functions...

 test replications elapsed relative user.self sys.self user.child sys.child
2 bubble_sort_r(input) 100 384.256 24.789 384.045 0 0.022 0.123
1 bubble_sort(input) 100 15.501 1.000 15.502 0 0.000 0.000

We see that, for vectors of 10k random numbers, the Rust implementation is nearly 25x faster than the R implementation, for the same task. That's a pretty massive speedup, considering we've used a fairly naive implementation of this algorithm (we're copying the vector at least twice). So, there will definitely be situations where you will save a huge amount of processing time by implementing functions in Rust (just like if you were implementing an underlying function in C or C++), with the added safety guarantees of Rust. You may find you are even able to perform calculations that simply aren't feasible (at least not in any reasonable amount of time) in pure R. So, happy coding!

Extending extendr

You may encounter situations in which extendr does not behave as expected or support your use case (yet). As of 2021-03-28, extendr v0.2.0 doesn't support (so far as I can tell) passing in character vectors that may contain NA's or correctly giving back integer vectors with NA's (they get converted to 0). There are at least two different ways to address these issues as they arise:

Wrapping Rust Calls in R

One strategy is to 'wrap' the functions automatically generated by extendr to address these issues by modifying the input or output on the R side. For an example of this, see R/r-wrappers.R.

Implementing R <-> Rust Conversions in Rust

Another strategy is to create structs and types in Rust with appropriate trait implementations (particularly FromRobj and From<T> for Robj) in your Rust module. This has the added benefit of plugging directly into the extendr infrastructure. For an example of this, see src/rust/src/structs/char_vec.rs.

About

Call Rust functions from R using the extendr-api crate

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Minimal Example of Calling Rust from R

R build statusLicense: MIT

This is a template package to demonstrate how to call Rust from R using the extendr-api crate, based on the helloextendr package.

Installation

Before you can develop with this package, you need to install a working Rust toolchain. It's probably best to use rustup.

On Windows, you'll also have to add the i686-pc-windows-gnu and x86_64-pc-windows-gnu targets:

rustup target add x86_64-pc-windows-gnu
rustup target add i686-pc-windows-gnu

Since this project is intended to provide reference implementations, you will probably find more utility in cloning the repository than installing the package. If you insist, you can install rustbind with:

devtools::install_github("ericwburden/rustbind")

Development

To build this package, and generate the Rust function wrappers in R/extendr-wrappers.R, you should prefer to source the build.R build script over the 'Clean and Rebuild' menu option in RStudio.

Usage

In order to demonstrate the process of adding a simple Rust function, callable by R, via the extendr_api crate, consider the Bubble Sort algorithm. Briefly, Bubble Sorting requires traversing a list of elements and exchanging them pair-wise until the entire list is in order, as demonstrated in the below pseudocode.

function bubble_sort(arr) {
n = len(arr) // Traverse through all array elements for i in range from 0 to n {
for j in range from 0 to (n-i-1) { // For each element `j`, swap with element `j+1` if element `j` is
// larger
if arr[j] > arr[j+1] : swap(arr[j], arr[j+1])
}
}
}

This is not an efficient way to sort a list (or vector), but it is an algorithm that can be run much faster by implementing it in Rust instead of R alone.

Adding the Rust Implementation

To keep the code nicely separated, start by creating a file rustbind/src/rust/src/bubble_sort.rs with the following code:

use extendr_api::prelude::{Real,RobjItertools};pub(crate)fnbubble_sort_fn(input:Real) -> Real{letmut nvec:Vec<_> = input.collect();let len = nvec.len();for idx in0..(len - 1){let last_idx = len - idx - 1;for inner_idx in0..last_idx {if nvec[inner_idx] > nvec[inner_idx + 1]{
nvec.swap(inner_idx, inner_idx + 1)}}}
nvec.iter().collect_robj().as_real_iter().unwrap()}

In order to generate the wrappers and bindings, add the following to rustbind/src/rust/src/lib.rs:

use extendr_api::prelude::*;mod bubble_sort;// Other modules here.../// Bubble Sort a vector of doubles/// /// Demonstrates using Rust to perform a Bubble Sort on a vector of doubles/// /// @params input A double vector to sort/// @return a sorted vector of doubles/// /// @examples bubble_sort(runif(1000))/// /// @export#[extendr]fnbubble_sort(input:Real) -> Real{
bubble_sort::bubble_sort_fn(input)}extendr_module!{mod rustbind;// Other functions to export go here...fn bubble_sort;}

And that's it! After sourcing build.R in order to build, install, and load your package, you will be able to call yourpackagename::bubble_sort(n) and take advantage of Rust's blazing speed in your R code. Also, note the format of that doc comment. If you've written R packages before, you'll recognize those as roxygen2 comments, which are used to build the documentation for your package, as well as control whether or not the function is available outside your package (via package_name::function_name) through the '@export' tag.

Was it Worth It?

Now, that's definitely a relatively more complicated way to just write Rust code instead of R code, was it worth it? I would argue that for any type of exploratory or ad hoc analysis, the answer is most definitely no. But, if you're writing R code that will be used in production to run the same type of algorithm over and over again, the speed gains are tremendous. Consider the analogous R implementation of the Bubble Sort algorithm:

#' Bubble Sorting - R Implementation#'#' @param nums numeric vector to sort#' @return a sorted integer vector#' @exportbubble_sort_r<-function(nums) {
nums<-if (missing(nums)) { stats::runif(1000) }
n<- length(nums)
for (iin1:(n-1)) {
for (jin1:(n-i)) {
if (nums[j] >nums[j+1]) {
temp<-nums[j]
nums[j] <-nums[j+1]
nums[j+1] <-temp
}
}
}
nums
}

Notwithstanding the slight modification needed because R doesn't provide a convenient way to swap values without using a temporary variable, this is nearly identical to our Rust implementation. But, if we benchmark the two functions...

 test replications elapsed relative user.self sys.self user.child sys.child
2 bubble_sort_r(input) 100 384.256 24.789 384.045 0 0.022 0.123
1 bubble_sort(input) 100 15.501 1.000 15.502 0 0.000 0.000

We see that, for vectors of 10k random numbers, the Rust implementation is nearly 25x faster than the R implementation, for the same task. That's a pretty massive speedup, considering we've used a fairly naive implementation of this algorithm (we're copying the vector at least twice). So, there will definitely be situations where you will save a huge amount of processing time by implementing functions in Rust (just like if you were implementing an underlying function in C or C++), with the added safety guarantees of Rust. You may find you are even able to perform calculations that simply aren't feasible (at least not in any reasonable amount of time) in pure R. So, happy coding!

Extending extendr

You may encounter situations in which extendr does not behave as expected or support your use case (yet). As of 2021-03-28, extendr v0.2.0 doesn't support (so far as I can tell) passing in character vectors that may contain NA's or correctly giving back integer vectors with NA's (they get converted to 0). There are at least two different ways to address these issues as they arise:

Wrapping Rust Calls in R

One strategy is to 'wrap' the functions automatically generated by extendr to address these issues by modifying the input or output on the R side. For an example of this, see R/r-wrappers.R.

Implementing R <-> Rust Conversions in Rust

Another strategy is to create structs and types in Rust with appropriate trait implementations (particularly FromRobj and From<T> for Robj) in your Rust module. This has the added benefit of plugging directly into the extendr infrastructure. For an example of this, see src/rust/src/structs/char_vec.rs.

About

Call Rust functions from R using the extendr-api crate

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Minimal Example of Calling Rust from R

R build statusLicense: MIT

This is a template package to demonstrate how to call Rust from R using the extendr-api crate, based on the helloextendr package.

Installation

Before you can develop with this package, you need to install a working Rust toolchain. It's probably best to use rustup.

On Windows, you'll also have to add the i686-pc-windows-gnu and x86_64-pc-windows-gnu targets:

rustup target add x86_64-pc-windows-gnu
rustup target add i686-pc-windows-gnu

Since this project is intended to provide reference implementations, you will probably find more utility in cloning the repository than installing the package. If you insist, you can install rustbind with:

devtools::install_github("ericwburden/rustbind")

Development

To build this package, and generate the Rust function wrappers in R/extendr-wrappers.R, you should prefer to source the build.R build script over the 'Clean and Rebuild' menu option in RStudio.

Usage

In order to demonstrate the process of adding a simple Rust function, callable by R, via the extendr_api crate, consider the Bubble Sort algorithm. Briefly, Bubble Sorting requires traversing a list of elements and exchanging them pair-wise until the entire list is in order, as demonstrated in the below pseudocode.

function bubble_sort(arr) {
n = len(arr) // Traverse through all array elements for i in range from 0 to n {
for j in range from 0 to (n-i-1) { // For each element `j`, swap with element `j+1` if element `j` is
// larger
if arr[j] > arr[j+1] : swap(arr[j], arr[j+1])
}
}
}

This is not an efficient way to sort a list (or vector), but it is an algorithm that can be run much faster by implementing it in Rust instead of R alone.

Adding the Rust Implementation

To keep the code nicely separated, start by creating a file rustbind/src/rust/src/bubble_sort.rs with the following code:

use extendr_api::prelude::{Real,RobjItertools};pub(crate)fnbubble_sort_fn(input:Real) -> Real{letmut nvec:Vec<_> = input.collect();let len = nvec.len();for idx in0..(len - 1){let last_idx = len - idx - 1;for inner_idx in0..last_idx {if nvec[inner_idx] > nvec[inner_idx + 1]{
nvec.swap(inner_idx, inner_idx + 1)}}}
nvec.iter().collect_robj().as_real_iter().unwrap()}

In order to generate the wrappers and bindings, add the following to rustbind/src/rust/src/lib.rs:

use extendr_api::prelude::*;mod bubble_sort;// Other modules here.../// Bubble Sort a vector of doubles/// /// Demonstrates using Rust to perform a Bubble Sort on a vector of doubles/// /// @params input A double vector to sort/// @return a sorted vector of doubles/// /// @examples bubble_sort(runif(1000))/// /// @export#[extendr]fnbubble_sort(input:Real) -> Real{
bubble_sort::bubble_sort_fn(input)}extendr_module!{mod rustbind;// Other functions to export go here...fn bubble_sort;}

And that's it! After sourcing build.R in order to build, install, and load your package, you will be able to call yourpackagename::bubble_sort(n) and take advantage of Rust's blazing speed in your R code. Also, note the format of that doc comment. If you've written R packages before, you'll recognize those as roxygen2 comments, which are used to build the documentation for your package, as well as control whether or not the function is available outside your package (via package_name::function_name) through the '@export' tag.

Was it Worth It?

Now, that's definitely a relatively more complicated way to just write Rust code instead of R code, was it worth it? I would argue that for any type of exploratory or ad hoc analysis, the answer is most definitely no. But, if you're writing R code that will be used in production to run the same type of algorithm over and over again, the speed gains are tremendous. Consider the analogous R implementation of the Bubble Sort algorithm:

#' Bubble Sorting - R Implementation#'#' @param nums numeric vector to sort#' @return a sorted integer vector#' @exportbubble_sort_r<-function(nums) {
nums<-if (missing(nums)) { stats::runif(1000) }
n<- length(nums)
for (iin1:(n-1)) {
for (jin1:(n-i)) {
if (nums[j] >nums[j+1]) {
temp<-nums[j]
nums[j] <-nums[j+1]
nums[j+1] <-temp
}
}
}
nums
}

Notwithstanding the slight modification needed because R doesn't provide a convenient way to swap values without using a temporary variable, this is nearly identical to our Rust implementation. But, if we benchmark the two functions...

 test replications elapsed relative user.self sys.self user.child sys.child
2 bubble_sort_r(input) 100 384.256 24.789 384.045 0 0.022 0.123
1 bubble_sort(input) 100 15.501 1.000 15.502 0 0.000 0.000

We see that, for vectors of 10k random numbers, the Rust implementation is nearly 25x faster than the R implementation, for the same task. That's a pretty massive speedup, considering we've used a fairly naive implementation of this algorithm (we're copying the vector at least twice). So, there will definitely be situations where you will save a huge amount of processing time by implementing functions in Rust (just like if you were implementing an underlying function in C or C++), with the added safety guarantees of Rust. You may find you are even able to perform calculations that simply aren't feasible (at least not in any reasonable amount of time) in pure R. So, happy coding!

Extending extendr

You may encounter situations in which extendr does not behave as expected or support your use case (yet). As of 2021-03-28, extendr v0.2.0 doesn't support (so far as I can tell) passing in character vectors that may contain NA's or correctly giving back integer vectors with NA's (they get converted to 0). There are at least two different ways to address these issues as they arise:

Wrapping Rust Calls in R

One strategy is to 'wrap' the functions automatically generated by extendr to address these issues by modifying the input or output on the R side. For an example of this, see R/r-wrappers.R.

Implementing R <-> Rust Conversions in Rust

Another strategy is to create structs and types in Rust with appropriate trait implementations (particularly FromRobj and From<T> for Robj) in your Rust module. This has the added benefit of plugging directly into the extendr infrastructure. For an example of this, see src/rust/src/structs/char_vec.rs.

About

Call Rust functions from R using the extendr-api crate

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages