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) 需要几个步骤:

  1. 我们必须确保 C 指针不是NULL,因为 Rust 引用不能NULL。(会在 C 语言调用该函数,对应的,在 C 中,字符串只是指向一个char的指针,所以,该 Rust 函数的字符串参数,会迎来一个 C 指针。)

  2. 使用std::ffi::CStr包装指针。CStr将根据终止的NUL,计算字符串的长度。这需要一个unsafe区块,因为我们将解引用一个原始指针,Rust 编译器无法验证该指针,满足所有安全保证,因此程序员必须这样做。(unsafe的作用,不是说有多么特殊,只是为了让人们更快,且更好地注意到,存在安全隐患的代码。)

  3. 确保 C 字符串是有效的 UTF-8 ,并将其转换为 Rust 字符串切片。

  4. 使用字符串切片。

在这个例子中,如果我们的任何先决条件失败,我们只是简单中止程序。每个用例必须评估什么是适当的故障模式,但是 大声和尽早失败 是一个很好的起步。

所有权 和 生命周期

在这个例子中,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字节被嵌入字符串。