Rust 函数使用字符串参数
让我们开始一些更复杂的东西,接受字符串参数。在 Rust 中,字符串由一组u8
切片组成,并保证是有效的 UTF-8,允许NUL
字节,在字符串内部。在 C 中,字符串只是指向一个char
的指针,用一个NUL
字节作为终止 (带整数值0
) 。需要做一些转换工作,才能在处理好这两种表达。
extern crate libc;
use libc::{c_char, uint32_t};
use std::ffi::CStr;
use std::str;
#[no_mangle]
pub extern fn how_many_characters(s: *const c_char) -> uint32_t {
let c_str = unsafe {
assert!(!s.is_null());
CStr::from_ptr(s)
};
let r_str = c_str.to_str().unwrap();
r_str.chars().count() as uint32_t
}
获取一个 Rust 字符串切片 (&str
) 需要几个步骤:
-
我们必须确保 C 指针不是
NULL
,因为 Rust 引用不能NULL
。(会在 C 语言调用该函数,对应的,在 C 中,字符串只是指向一个char
的指针,所以,该 Rust 函数的字符串参数,会迎来一个 C 指针。) -
使用
std::ffi::CStr
包装指针。CStr
将根据终止的NUL
,计算字符串的长度。这需要一个unsafe
区块,因为我们将解引用一个原始指针,Rust 编译器无法验证该指针,满足所有安全保证,因此程序员必须这样做。(unsafe
的作用,不是说有多么特殊,只是为了让人们更快,且更好地注意到,存在安全隐患的代码。) -
确保 C 字符串是有效的 UTF-8 ,并将其转换为 Rust 字符串切片。
-
使用字符串切片。
在这个例子中,如果我们的任何先决条件失败,我们只是简单中止程序。每个用例必须评估什么是适当的故障模式,但是 大声和尽早失败 是一个很好的起步。
所有权 和 生命周期
在这个例子中,Rust 代码确实 没有 拥有字符串切片,编译器只允许字符串与CStr
实例生命一样。程序员应该确保这个生命周期足够短。
C
#include <stdio.h>
#include <inttypes.h>
extern uint32_t how_many_characters(const char *str);
int main(void) {
uint32_t count = how_many_characters("göes to élevên");
printf("%u\n", count);
return 0;
}
C 代码声明函数,接受指向常量字符串的指针,因为 Rust 函数不会改字符串。然后,您可以使用正常的 C 字符串常量,调用该函数。
Ruby
# coding: utf-8
require 'ffi'
module StringArguments
extend FFI::Library
ffi_lib 'string_arguments'
attach_function :how_many_characters, [:string], :uint32
end
puts StringArguments.how_many_characters("göes to élevên")
FFI 自动将 Ruby 字符串,转换为适当的 C 字符串.
Python
#!/usr/bin/env python3
# coding: utf-8
import sys, ctypes
from ctypes import c_uint32, c_char_p
prefix = {'win32': ''}.get(sys.platform, 'lib')
extension = {'darwin': '.dylib', 'win32': '.dll'}.get(sys.platform, '.so')
lib = ctypes.cdll.LoadLibrary(prefix + "string_arguments" + extension)
lib.how_many_characters.argtypes = (c_char_p,)
lib.how_many_characters.restype = c_uint32
print(lib.how_many_characters("göes to élevên".encode('utf-8')))
Python 字符串,必须编码为 UTF-8,才能通过 FFI 边界.
Haskell
{-# LANGUAGE ForeignFunctionInterface #-}
import Data.Word (Word32)
import Foreign.C.String (CString(..), newCString)
foreign import ccall "how_many_characters"
how_many_characters :: CString -> Word32
main :: IO ()
main = do
str <- newCString "göes to élevên"
print (how_many_characters str)
该Foreign.C.String
模块支持将 Haskell 的字符串描述,转换为 C 包装字节的描述。我们可以创建一个带newCString
的函数,然后传递CString
到我们的外部函数。
Node.js
const ffi = require('ffi');
const lib = ffi.Library('libstring_arguments', {
how_many_characters: ['uint32', ['string']],
});
console.log(lib.how_many_characters('göes to élevên'));
该ffi
包自动将 JavaScript 字符串,转换为适当的 C 字符串。
C\
using System;
using System.Runtime.InteropServices;
class StringArguments
{
[DllImport("string_arguments", EntryPoint="how_many_characters")]
public static extern uint HowManyCharacters(string s);
static public void Main()
{
var count = StringArguments.HowManyCharacters("göes to élevên");
Console.WriteLine(count);
}
}
原生字符串会自动整理为 与 C 兼容的字符串。
Julia
#!/usr/bin/env julia
using Libdl
libname = "string_arguments"
if !Sys.iswindows()
libname = "lib$(libname)"
end
libstring_arguments = Libdl.dlopen(libname)
howmanycharacters_sym = Libdl.dlsym(libstring_arguments, :how_many_characters)
howmanycharacters(s:: AbstractString) = ccall(
howmanycharacters_sym,
UInt32, (Cstring,),
s)
println(howmanycharacters("göes to élevên"))
Julia 字符串 (AbstractString
的基础类型) 自动转换为 C 字符串。 这个来自 Julia 的 Cstring
类型与 Rust 的CStr
类型兼容, 因为它也假设一个NUL
终止字节
,且不允许NUL
字节被嵌入字符串。