17个不稳定版本 (5个破坏性版本)

0.7.0 2024年6月29日
0.6.0 2023年10月6日
0.4.0 2023年2月26日
0.3.1 2022年5月30日
0.1.0 2020年7月5日

#50 in Rust模式

Download history 516/week @ 2024-05-03 950/week @ 2024-05-10 770/week @ 2024-05-17 766/week @ 2024-05-24 839/week @ 2024-05-31 986/week @ 2024-06-07 1138/week @ 2024-06-14 1338/week @ 2024-06-21 1661/week @ 2024-06-28 1171/week @ 2024-07-05 736/week @ 2024-07-12 668/week @ 2024-07-19 490/week @ 2024-07-26 1334/week @ 2024-08-02 433/week @ 2024-08-09 695/week @ 2024-08-16

3,108 每月下载量
用于 4 crates

MIT 许可证

630KB
13K SLoC

extendr-api

一个安全且用户友好的R扩展接口。

  • 构建R扩展到R。
  • 将R包转换为Rust crate。

这个库旨在为Rust或任何编译语言的新手用户提供一个熟悉的接口。

安装

只需将此行添加到您的 [dependencies] 部分 Cargo.toml。然后您将能够从Rust中调用R代码。

[dependencies]
extendr-api = "0.6"

概述

参阅 [Robj],了解此crate的大部分内容。[Robj] 为R对象类型提供了一个安全的包装器。

使用属性和宏来导出到R。

use extendr_api::prelude::*;
// Export a function or impl to R.
#[extendr]
fn fred(a: i32) -> i32 {
    a + 1
}

// define exports using extendr_module
extendr_module! {
   mod mymodule;
   fn fred;
}

在R中

result <- fred(1)

[Robj] 是R对象的包装器。r!() 和 R!() 宏允许您分别使用Rust和R语法构建R对象。

use extendr_api::prelude::*;
test! {
    // An R object with a single string "hello"
    let character = r!("hello");
    let character = r!(["hello", "goodbye"]);

    // An R integer object with a single number 1L.
    // Note that in Rust, 1 is an integer and 1.0 is a real.
    let integer = r!(1);

    // An R real object with a single number 1.
    // Note that in R, 1 is a real and 1L is an integer.
    let real = r!(1.0);

    // An R real vector.
    let real_vector = r!([1.0, 2.0]);
    let real_vector = &[1.0, 2.0].iter().collect_robj();
    let real_vector = r!(vec![1.0, 2.0]);

    // An R function object.
    let function = R!("function(x, y) { x + y }")?;

    // A named list using the list! macro.
    let list = list!(a = 1, b = 2);

    // An unnamed list (of R objects) using the List wrapper.
    let list = r!(List::from_values(vec![1, 2, 3]));
    let list = r!(List::from_values(vec!["a", "b", "c"]));
    let list = r!(List::from_values(&[r!("a"), r!(1), r!(2.0)]));

    // A symbol
    let sym = sym!(wombat);

    // A R vector using collect_robj()
    let vector = (0..3).map(|x| x * 10).collect_robj();
}

在Rust中,我们更喜欢使用迭代器而不是循环。

use extendr_api::prelude::*;
test! {
    // 1 ..= 100 is the same as 1:100
    let res = r!(1 ..= 100);
    assert_eq!(res, R!("1:100")?);

    // Rust arrays are zero-indexed so it is more common to use 0 .. 100.
    let res = r!(0 .. 100);
    assert_eq!(res.len(), 100);

    // Using map is a super fast way to generate vectors.
    let iter = (0..3).map(|i| format!("fred{}", i));
    let character = iter.collect_robj();
    assert_eq!(character, r!(["fred0", "fred1", "fred2"]));
}

要索引一个向量,首先将其转换为切片,然后记得使用基于0的索引。在Rust中,越界将会导致错误(panic),而C++可能会崩溃。

use extendr_api::prelude::*;
test! {
    let vals = r!([1.0, 2.0]);
    let slice = vals.as_real_slice().ok_or("expected slice")?;
    let one = slice[0];
    let two = slice[1];
    // let error = slice[2];
    assert_eq!(one, 1.0);
    assert_eq!(two, 2.0);
}

这些方法更慢,但更通用

use extendr_api::prelude::*;
test! {
    let vals = r!([1.0, 2.0, 3.0]);

    // one-based indexing [[i]], returns an object.
    assert_eq!(vals.index(1)?, r!(1.0));

    // one-based slicing [x], returns an object.
    assert_eq!(vals.slice(1..=2)?, r!([1.0, 2.0]));

    // $ operator, returns an object
    let list = list!(a = 1.0, b = "xyz");
    assert_eq!(list.dollar("a")?, r!(1.0));
}

[R!] 宏允许您在Rust中嵌入R代码,并使用 {{ }} 对对Rust表达式进行包装。

[Rraw!] 宏不会展开 {{ }} 对对。

use extendr_api::prelude::*;
test! {
    // The text "1 + 1" is parsed as R source code.
    // The result is 1.0 + 1.0 in Rust.
    assert_eq!(R!("1 + 1")?, r!(2.0));

    let a = 1.0;
    assert_eq!(R!("1 + {{a}}")?, r!(2.0));

    assert_eq!(R!(r"
        x <- {{ a }}
        x + 1
    ")?, r!(2.0));

    assert_eq!(R!(r#"
        x <- "hello"
        x
    "#)?, r!("hello"));

    // Use the R meaning of {{ }} and do not expand.
    assert_eq!(Rraw!(r"
        x <- {{ 1 }}
        x + 1
    ")?, r!(2.0));
}

[r!] 宏将Rust对象转换为R对象,并接受参数。

use extendr_api::prelude::*;
test! {
    // The text "1.0+1.0" is parsed as Rust source code.
    let one = 1.0;
    assert_eq!(r!(one+1.0), r!(2.0));
}

您可以使用 [call!] 宏调用R函数和原语。

use extendr_api::prelude::*;
test! {

    // As one R! macro call
    let confint1 = R!("confint(lm(weight ~ group - 1, PlantGrowth))")?;

    // As many parameterized calls.
    let formula = call!("~", sym!(weight), lang!("-", sym!(group), 1))?;
    let plant_growth = global!(PlantGrowth)?;
    let model = call!("lm", formula, plant_growth)?;
    let confint2 = call!("confint", model)?;

    assert_eq!(confint1.as_real_vector(), confint2.as_real_vector());
}

Rust有一个“所有者”和“借用”对象的概念。

拥有的对象,例如 [Vec] 和 [String],在对象生命周期结束时释放内存。

借用对象,如 &[i32] 和 &str,只是指向另一个对象内存的指针,它们的寿命不能超过它们所引用的对象。

借用对象比拥有的对象快得多,并且使用更少的内存,但仅用于临时访问。

例如,当我们从 R 向量中取一个切片时,我们需要原始 R 对象保持存活,否则数据将损坏。

use extendr_api::prelude::*;
test! {
    // robj is an "Owned" object that controls the memory allocated.
    let robj = r!([1, 2, 3]);

    // Here slice is a "borrowed" reference to the bytes in robj.
    // and cannot live longer than robj.
    let slice = robj.as_integer_slice().ok_or("expected slice")?;
    assert_eq!(slice.len(), 3);
}

功能门

extendr-api 在这些功能门后面有一些可选功能

  • ndarray:提供 R 矩阵和 ndarray 之间的转换。
  • num-complex:提供 R 复数和 num-complex 之间的转换。
  • serde:提供 serde 支持。
  • graphics:提供控制或实现图形设备的功能。
  • either:提供 Either<L, R>either 的类型转换特质的实现,如果 LR 都实现了这些特质。
  • faer:提供 R 矩阵和 faer 之间的转换。

extendr-api 对 Result<T,E>Robj 有不同的编码(转换)。在下面的 x_ok 代表从 rust 通过 T::into_robj() 或类似方式返回的 R 侧的 R 变量。同样,x_err 通过 E::into_robj() 或类似方式从 rust 返回到 R 侧。extendr-api

  • result_list' Ok(T) 被编码为 list(ok = x_ok, err = NULL),而 Err 被编码为 list(ok = NULL, err = e_err)
  • result_condition' Ok(T) 被编码为 x_ok,而 Err(E) 被编码为 condition(msg="extendr_error", value = x_err, class=c("extendr_error", "error", "condition"))
  • 以上结果功能门的倍数。只有一个结果功能门将生效,当前优先级为[result_listresult_condition,... ]。

最后,R的API中有一部分被视为非API,因此建议CRAN上的R包不提供这些功能。如果您想访问它们,可以使用non-api来暴露它们。但是,需要设置bindgen,并具体遵循以下libR-sys的设置说明。

许可证

MIT

依赖关系

~4–7MB
~167K SLoC