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
}
转换一个数组分为两步:
-
确保 C 指针不是
NULL
,因 Rust 引用(可能)永远不会NULL
。 -
使用
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));
我们需要使用ref
和ref-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
与长度传递。