Rust 函数:切片参数

Rust 切片指针的概念,与一块定量元素的数据捆绑在一起。在 C 中,数组由相同的部分组成,但没有标准容器将它们维系在一起。

extern crate libc;

use libc::{uint32_t, size_t};
use std::slice;

#[no_mangle]
pub extern fn sum_of_even(n: *const uint32_t, len: size_t) -> uint32_t {
    let numbers = unsafe {
        assert!(!n.is_null());

        slice::from_raw_parts(n, len as usize)
    };

    let sum =
        numbers.iter()
        .filter(|&v| v % 2 == 0)
        .fold(0, |acc, v| acc + v);
    sum as uint32_t
}

转换一个数组分为两步:

  1. 确保 C 指针不是NULL,因 Rust 引用(可能)永远不会NULL

  2. 使用from_raw_parts将指针和长度,转换为一个切片。这是一种不安全的操作,因为可能会,解引用了无效内存。

C

#include <stdio.h>
#include <stdint.h>

extern uint32_t sum_of_even(const uint32_t *numbers, size_t length);

int main(void) {
  uint32_t numbers[6] = {1,2,3,4,5,6};
  uint32_t sum = sum_of_even(numbers, 6);
  printf("%u\n", sum);
  return 0;
}

C 的调用是直截了当的,因为我们使 Rust 代码与 C 的能力相匹配。唯一的复杂因素是确保定义的,和函数调用的元素数量相同。

Ruby

require 'ffi'

module SliceArgumentsFFI
  extend FFI::Library
  ffi_lib 'slice_arguments'
  attach_function :sum_of_even, [:pointer, :size_t], :uint32
end

class SliceArguments
  extend SliceArgumentsFFI

  def self.sum_of_even(numbers)
    buf = FFI::MemoryPointer.new(:uint32, numbers.size)
    buf.write_array_of_uint32(numbers)
    super(buf, numbers.size)
  end
end

puts SliceArguments.sum_of_even([1,2,3,4,5,6])

Ruby 的调用比起以前的示例,需要更多工作。这一次,我们用MemoryPointer 来分配空间,来存储我们的整数. 创建后,我们使用write_array_of_uint32,将值复制进去。

Python

#!/usr/bin/env python3

import sys, ctypes
from ctypes import POINTER, c_uint32, c_size_t

prefix = {'win32': ''}.get(sys.platform, 'lib')
extension = {'darwin': '.dylib', 'win32': '.dll'}.get(sys.platform, '.so')
lib = ctypes.cdll.LoadLibrary(prefix + "slice_arguments" + extension)

lib.sum_of_even.argtypes = (POINTER(c_uint32), c_size_t)
lib.sum_of_even.restype = ctypes.c_uint32

def sum_of_even(numbers):
    buf_type = c_uint32 * len(numbers)
    buf = buf_type(*numbers)
    return lib.sum_of_even(buf, len(numbers))

print(sum_of_even([1,2,3,4,5,6]))

Python 的调用比起以前的示例,需要更多工作。 这次,我们创建一个新类型来存储我们的整数,并用值(*numbers),实例化这个类型。

Haskell

{-# LANGUAGE ForeignFunctionInterface #-}

import Data.Word (Word32)
import Foreign (Ptr)
import Foreign.Marshal.Array (withArrayLen)

foreign import ccall "sum_of_even"
  sum_of_even :: Ptr Word32 -> Word32 -> Word32

main :: IO ()
main = withArrayLen [1,2,3,4,5,6] $ \len arr ->
    print (sum_of_even arr (fromIntegral len))

对于这个例子,我们可以使用withArrayLen函数,它会拿到内容为Storable (即可序列化为 C 可以理解的字节序列) 的 Haskell 数组,并生成这些值的打包数组,然后将其与数组的长度,一起传递给回调函数。 在这种情况下,要将数组的长度作为Int类型传递,这里,我们会通过fromIntegral函数,转换成预期的CUInt

Node.js

const ffi = require('ffi');
const ref = require('ref');
const array = require('ref-array');

const U32array = array(ref.types.uint32);

const lib = ffi.Library('libslice_arguments', {
  sum_of_even: ['uint32', [U32array, 'size_t']],
});

const numbers = new U32array([1, 2, 3, 4, 5, 6]);
console.log(lib.sum_of_even(numbers, numbers.length));

我们需要使用refref-array,用于将 node.js 内存缓冲(buffer),包装成类似数组的对象,这些对象可以通过 JavaScript 轻松操作。该u32array类型 (来自ref.types的原始构造) 可以之后,在函数签名中使用。

C\

using System;
using System.Runtime.InteropServices;

class SliceArguments
{
    [DllImport("slice_arguments")]
    private static extern uint sum_of_even(int[] n, UIntPtr len);

    public static uint SumOfEven(int[] n)
    {
        return sum_of_even(n, (UIntPtr)n.Length);
    }

    static public void Main()
    {
        var sum = SliceArguments.SumOfEven(new [] {1,2,3,4,5,6});
        Console.WriteLine(sum);
    }
}

传递数组很复杂,因为我们需要传递一个指向数据的指针 以及 数据数组的长度。与前面的例子不同,我们引入了sum_of_even函数(不是约定的编写风格),作为私人方法. 然后我们可以添加一个公共方法,它包装私有方法,并提供预期的接口。

C 代码使用了size_t,一种大小根据平台而变化的类型。为了仿造这一点,我们使用了一个UIntPtr

Julia

#!/usr/bin/env julia
using Libdl

libname = "slice_arguments"
if !Sys.iswindows()
    libname = "lib$(libname)"
end

lib = Libdl.dlopen(libname)
sumofeven_sym = Libdl.dlsym(lib, :sum_of_even)

sumofeven(a:: Array{UInt32}) = ccall(
    sumofeven_sym,
    UInt32,
    (Ptr{UInt32}, Csize_t),
    a,
    length(a))

println(sumofeven(UInt32[1, 2, 3, 4, 5, 6]))

传递数组,需要一个指针和数组的长度。该数组被隐式转换为,指向第一个元素的指针,类型为Ptr{UInt32}Csize类型是 Julia 的size_t等价物。我们还强制让函数参数类型为Array{UInt32},这是原生函数有效兼容的特定数组类型。

这个例子有点复杂,因为在 Julia 中,有两个的原始指针类型。 Ptr{T}是一个T类型值的普通内存地址,而Ref{T} 是一个托管指针,指向 Julia 垃圾收集器不会放手的数据,只要这个Ref还引用。任何情况下,这些类型会在ccall,转换为 C 兼容指针类型。

虽然, 在传递指向原生函数的参数时,首选Ref ,甚至可通过可变指针传递它们(可让原生函数,修改指向的对象),C 风格的数组是一个例外规则,应该用普通的Ptr与长度传递。