从 R 调用 Rust:错误“需要向量类型。”

bre*_*auv 6 r rust rextendr extendr

rextendr我正在尝试使用R 包和 Rust箱来学习如何连接 Rust 和 R(除了学习 Rust 本身)extendr。当我在 R 函数的输入中Expected a vector type.使用时遇到错误。:

\n

下面是一个简单函数的示例,TRUE如果输入向量中的所有值都为正,则返回,FALSE否则返回。当我在输入中使用时它似乎工作正常c(),但当我使用:.

\n
library(rextendr)\n\n# create a Rust function that checks if all values in the input are\n# greater than 0\nrust_function(\n  "fn all_positive(input: &[f64]) -> bool {\n      let mut out = true;\n      for i in input.iter() {\n        if *i <= 0.0 {\n          out = false;\n          break\n        }  \n      }\n      out\n  }"\n)\n#> \xe2\x84\xb9 build directory: \'/tmp/RtmpeoMSEH/file7f971642e99e\'\n#> \xe2\x9c\x94 Writing \'/tmp/RtmpeoMSEH/file7f971642e99e/target/extendr_wrappers.R\'.\n\n# call it from R\nall_positive(c(1, 2, 3))\n#> [1] TRUE\n\nall_positive(c(0, 1, 2))\n#> [1] FALSE\n\nall_positive(1:3)\n#> Error in all_positive(1:3): Expected a vector type.\n
Run Code Online (Sandbox Code Playgroud)\n

这是为什么?我该如何解决它?

\n

PS:由于我是从 Rust 开始的,请随意提及 Rust 代码中的任何其他错误/非惯用的内容。

\n

Sam*_*amR 4

你需要一个接受integernumeric类型的Rust 函数

1:3创建一个integer向量并c(1,2,3)创建一个numeric向量。您的示例中的函数需要f64,即numeric输入,因此不喜欢1:3(这将是i32)。您可以通过将函数参数设置为 来编写一个接受两者的函数Robj,例如

rextendr::rust_function(
    "fn all_positive(input: Robj) -> extendr_api::scalar::Rbool {
    let float_vec: Option<Vec<f64>> = input.as_real_vector();
    let int_vec: Option<Vec<i32>> = input.as_integer_vector();

    match float_vec {
        Some(vec) => return extendr_api::scalar::Rbool::from_bool(vec.iter().all(|x: &f64| x > &0.0)),
        None => (),
    }

    match int_vec {
        Some(vec) => return extendr_api::scalar::Rbool::from_bool(vec.iter().all(|x: &i32| x > &0)),
        None => (),
    }
    extendr_api::scalar::Rbool::na_value()
}
"
)
Run Code Online (Sandbox Code Playgroud)

编辑:现在返回一个 Rbool 而不是 Rust bool ,因此如果您使用非数字类型(例如字符向量)调用该函数,它可以返回 NA_logical_

更多详情

这就是 tl;dr。这是更多信息。这里有三个因素在起作用:

  1. 运算:符意味着 R 创建一个向量integer而不是一个numeric向量。
  2. Rust 是一种静态类型语言。您的问题中定义的函数需要一个f64值的数组切片。更一般地说,Rust 要求函数参数是显式类型,或者是具有共享特征的泛型
  3. extendr 根据设计,不支持泛型。

integervs numeric: a 有何:不同

正如Dirk Eddelbuettel在评论中指出的那样,按照惯例:,运算符意味着一个整数向量(甚至在 之前ALTREP):

class(c(1,2,3)) # numeric
class(1:3) # integer
Run Code Online (Sandbox Code Playgroud)

我们可以使用该包查看底层的 C 表示lobstr

lobstr::sxp(1:3) 
# <INTSXP[3]> (altrep named:65535)

lobstr::sxp(c(1,2,3)) 
#  <REALSXP[3]> (named:2)

Run Code Online (Sandbox Code Playgroud)

正如R 内部结构 中所述,anINTSXP是 C 值块int。AREALSXP是 C 值的块double

翻译INTSXPREALSXP进入 Rust 世界

我们可以使用extendr-api,extendr-engineextendr-macroscrates ( v.0.4.0) 直接从 Rust 中查看如何使用extendr映射 R 类型:R!

fn main() {
    test! {

    let r_vec = R!("c(1,2,3)")?;
    let r_altrep = R!("1:3")?;

    println!("{}", Robj::is_altrep(&r_altrep)); // true

    println!("{:?}", r_altrep); // [1,2,3]
    println!("{:?}", r_vec); // [1.0, 2.0, 3.0]

    }
}
Run Code Online (Sandbox Code Playgroud)

这种快速而肮脏的调试打印确认该1:3对象被打印为整数集合 ( i32),而c(1,2,3)打印为浮点数,即f64在 Rust 世界中。

泛型

现在我们知道我们正在处理不同的类型,我的诱惑是做类似的事情:

fn all_positive_iter<I>(r_obj: I) -> bool
where
    I: IntoIterator,
{
   // some code here
}
Run Code Online (Sandbox Code Playgroud)

然而,尽管这会在 Rust 中编译,但它不会编译为导出extendr函数。

integer创建一个接受并numeric输入类型的Rust 函数

extendr关于缺乏泛型支持的 Github 问题建议创建一个 Rust 函数,该函数采用Robj,而不是原始 Rust 类型,并在 Rust 中解决这个问题。在这种情况下,我们可以使用Robj::as_integer_vector()Robj::as_real_vector方法。

这些的文档似乎正在进行中,但我们可以从源代码中看到它们返回Option<T>类型,即Some(<T>)None。我们可以使用该match构造尝试将从 R 接收到的内容转换为整数和浮点向量,并且仅在获取Some()类型时执行我们想要执行的操作。

顺便说一句,Rust 的一大优点是它与 R 非常相似,通常能够使用迭代器而不是循环。我想我们可以用你的代码的主要部分来做到这一点。我们可以使用与iter.all()文档非常相似的代码:

let a = [1, 2, 3];

assert!(a.iter().all(|&x| x > 0));
Run Code Online (Sandbox Code Playgroud)

如果您不想进行两次比较,您也可以键入cast the i32to ,尽管我们可以在我没有打扰的语句中链接the。f64.iter()match

1:3然后,这将允许我们调用在开始时为和定义的函数c(1,2,3)

all_positive(c(1, 2, 3))
#> [1] TRUE

all_positive(c(0, 1, 2))
#> [1] FALSE

all_positive(1:3)
#> [1] TRUE
Run Code Online (Sandbox Code Playgroud)

  • @SamR 我问了“extendr”开发人员,他们说你当前的答案很好 (2认同)