从R呼叫Rust:错误“应为向量类型,”

c86crjj0  于 2023-03-08  发布在  其他
关注(0)|答案(1)|浏览(121)
    • bounty将在2天后过期**。回答此问题可获得+50声望奖励。bretauv希望引起更多人对此问题的关注:我在使用{rextendr}包时没有找到任何关于R-Rust连接的问题和答案。希望这个非常基本的问题对未来的用户有帮助。

我正在学习如何使用rextendr R包和extendr Rust板条箱连接Rust和R(除了学习Rust本身)。当我在R函数的输入中使用:时,我遇到了错误Expected a vector type.
下面是一个简单函数的例子,如果输入向量中的所有值都是正的,则返回TRUE,否则返回FALSE。当我在输入中使用c()时,它似乎工作得很好,但当我使用:时就不行了。

library(rextendr)

# create a Rust function that checks if all values in the input are
# greater than 0
rust_function(
  "fn all_positive(input: &[f64]) -> bool {
      let mut out = true;
      for i in input.iter() {
        if *i <= 0.0 {
          out = false;
          break
        }  
      }
      out
  }"
)
#> ℹ build directory: '/tmp/RtmpeoMSEH/file7f971642e99e'
#> ✔ Writing '/tmp/RtmpeoMSEH/file7f971642e99e/target/extendr_wrappers.R'.

# call it from R
all_positive(c(1, 2, 3))
#> [1] TRUE

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

all_positive(1:3)
#> Error in all_positive(1:3): Expected a vector type.

为什么会这样?我该如何解决?

  • PS:因为我是从Rust开始的,所以请随意提及Rust代码中的任何其他错误/非惯用的东西。*
mzsu5hc0

mzsu5hc01#

需要接受integernumeric类型的Rust函数

1:3创建一个integer向量,c(1,2,3)创建一个numeric向量。示例中的函数需要f64,即numeric输入,因此不喜欢1:3。您可以编写一个同时接受这两个向量的函数,例如:

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()
}
"
)
  • Edit:现在它返回一个 * Rbool,而不是Rustbool *,这样如果你用一个非数值类型调用函数,例如字符向量,它可以返回 * NA_logical_

更多详情

这是TL;医生,这里有更多的信息。这里有三个因素在起作用:

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

integernumeric:带来了多大的不同

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

class(c(1,2,3)) # numeric
class(1:3) # integer

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

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

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

R Internals中所述,INTSXP是C int值的块,REALSXP是C double值的块。

INTSXPREALSXP翻译成Rust世界

我们可以使用extendr-apiextendr-engineextendr-macros板条箱(v.0.4.0)直接从Rust看到extendr如何MapR类型,使用R! macro

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]

    }
}

这个简单的调试输出确认1:3对象被输出为整数集合(i32),而c(1,2,3)被输出为浮点数,即Rust世界中的f64

泛型

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

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

但是--虽然这将在Rust中编译,但它不会编译为导出的extendr函数。

创建采用integernumeric类型的Rust函数

一个extendr Github的问题是缺少泛型支持suggests,创建一个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));

如果你不想有两个比较,你也可以type casti32f64,尽管我们可以在match语句中链接.iter(),我没有打扰。
这将允许我们为1:3c(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

相关问题