将 R 对象传递给 Rust 程序需要哪些步骤?

What are the steps necessary to pass R objects to a Rust program?

R和Rust都可以和C代码接口,所以我觉得很有可能。但是,我有点不清楚如何进行。

我已阅读这些部分以寻找答案:

  1. R-extensions System-and-foreign-language-interfaces
  2. The Rust foreign function interface guide

但是,虽然我精通 R,但我 不是 系统程序员,并且对构建链看起来像这样的努力感到困惑。

使用 Rinternals.h 是最理想的,但我也会选择更简单的 .C 界面。

如果 R 可以与 C 代码接口,那么从公开 C 风格函数的 Rust 代码编译共享库完全没有问题。

然后您就可以像使用 C 或 C++ 编写的库一样轻松地使用它了。当然,你不能直接从 R 中使用 Rust 对象和库,你必须制作适当的 C 接口来转换它们的功能。

下面是我如何为 SBCL 做到这一点,我想它与 R 非常相似:

Rust 方面

一些代码:

% cat experiment.rs

extern crate libc;

use libc::{c_int, c_char};
use std::{ffi, str};

#[no_mangle]
pub extern fn rust_code_string_to_int(s: *const c_char, r: *mut c_int) -> c_int { 
    let string = String::from_utf8_lossy(unsafe { ffi::CStr::from_ptr(s).to_bytes() });
    match <isize as str::FromStr>::from_str(&*string) {
        Ok(value) => { unsafe { *r = value as c_int }; 0 },
        Err(_) => -1,
    }
}

然后我正在制作共享库:

% rustc --crate-type dylib experiment.rs
% nm -a libexperiment.dylib | grep rust_code_string_to_int
0000000000001630 t __ZN23rust_code_string_to_int10__rust_abiE
00000000000015e0 T _rust_code_string_to_int

接下来,在 SBCL 端

现在我只是加载我的共享库,然后我可以访问我的 rust_code_string_to_int 函数:

RUST> (sb-alien:load-shared-object "libexperiment.dylib")
#P"libexperiment.dylib"
RUST> (sb-alien:with-alien ((result sb-alien:int 0))  
          (values (sb-alien:alien-funcall (sb-alien:extern-alien "rust_code_string_to_int" 
                                                                 (sb-alien:function sb-alien:int 
                                                                                    (sb-alien:c-string :external-format :utf-8)
                                                                                    (sb-alien:* sb-alien:int)))
                                          (sb-alien:make-alien-string "42")
                                          (sb-alien:addr result))
                  result))
0
42

我也为此苦苦挣扎了一段时间,但一旦你知道我是怎么做到的,其实并不那么困难。

首先按照以下说明创建一个 Rust 库:rust-inside-other-languages。 这是一个示例 Rust 库:

//src/lib.rs

#[no_mangle]
pub fn kelvin_to_fahrenheit(n: f64) -> f64 {
    n * 9.0/5.0 - 459.67
}

如果您按照 rust-inside-other-languages 中的说明进行操作,那么您应该能够生成 *.so(或 *.dll.dylib,具体取决于您的系统)。假设这个编译后的文件名为 libtempr.so.

现在创建一个 C++ 文件,它将把你需要的函数传递给 R:

//embed.cpp

extern "C" {
    double kelvin_to_fahrenheit(double);
}

// [[Rcpp::export]]
double cpp_kelvin_to_fahrenheit(double k) {
  double f = kelvin_to_fahrenheit(k);
  return(f);
}

现在在启动 R 之前,请确保环境变量 LD_LIBRARY_PATH 包含先前生成的共享对象 (libtempr.so) 的存储目录。在 shell 中执行:

$ export LD_LIBRARY_PATH=/home/sam/path/to/shared/object:$LD_LIBRARY_PATH
$ rstudio # I highly recommend using Rstudio for your R coding

最后在Rstudo中写入这个文件:

library(Rcpp)

Sys.setenv("PKG_LIBS"="-L/home/sam/path/to/shared/object -ltempr")

sourceCpp("/home/sam/path/to/embed.cpp", verbose = T, rebuild = T)

cpp_kelvin_to_fahrenheit(300)
  • 注意 Sys.setenv 中的 -L 选项指向包含您的 Rust 共享对象的目录。
  • 另外请注意,-l 选项是没有 lib 前缀且没有 .so(或系统上的任何内容)后缀的共享对象的名称。
  • 在 R 中使用 Sys.setenv 设置 LD_LIBRARY_PATH 变量 不起作用 。在启动 R 之前导出变量。
  • verbose 选项在那里,因此您可以看到 Rcpp 对编译 C++ 文件的作用。请注意上面 PKG_LIBS 中的选项是如何用于编译 C++ 文件的。
  • 每次 运行 这行 R 代码时,rebuild 选项都会强制重建 C++ 文件。

如果一切顺利,那么在交互式控制台中 运行 上面的 R 文件,当你到达最后一行时它应该输出 80.33

如果有什么不清楚的地方,请在评论中提问,我会尽量改进我的答案。

希望对您有所帮助:)


最后一点,基本函数 dyn.load.C 可以用作替代方法。但这需要编写比这种方法更多的样板包装代码。