类型

基础类型

Rust的标准库定了和C对应的类型

C type Corresponding std::os::raw type
short c_short
int c_int
long c_long
long long c_longlong
unsigned short c_ushort
unsigned, unsigned int c_uint
unsigned long c_ulong
unsigned long long c_ulonglong
char c_char
signed char c_schar
unsigned char c_uchar
float c_float
double c_double
void , const void mut c_void, const c_void
  • 除了c_void,其他的Rust类型都是Rust基础类型的别名,c_char是i8或u8。
  • rust的bool和c的bool相同。
  • rust的char和C的wchar_t不同,和char_32比较接近,但char_32不一定是Unicode。
  • rust的usize,isize和C的size_t,ptrdiff_t有相同的表示
  • C/C++的指针,C++的引用对应Rust的mut T和const T
  • 理论上C允许使用Rust没有对应类型的表示,但现在所有的Rust实现都有对应的类型。

结构

C会保证内存里字段的顺和定义一样,但Rust一般会重新排序来最小化内存占用,而且还有不占内存的类型。定义结构体是可以用 #[repr(C)] 告诉Rust将结构体的内存模拟的C的内存结构。#[repr(C)]只影响整个结构体的内存结构,不影响字段的。所以每个字段还需要用和C对应的类型。

  1. use std::os::raw::{c_char, c_int};
  2. #[repr(C)]
  3. pub struct git_error {
  4. pub message: *const c_char,
  5. pub klass: c_int
  6. }

对应C的

  1. typedef struct {
  2. char *message;
  3. int klass;
  4. }

枚举和union

定义C风格的枚举

  1. #[repr(C)]
  2. #[allow(non_camel_case_types)]
  3. enum git_error_code {
  4. GIT_OK = 0,
  5. GIT_ERROR = -1,
  6. GIT_ENOTFOUND = -3,
  7. GIT_EEXISTS = -4,
  8. ...
  9. }

一般Rust会用各种手段来选择枚举的表示方法,上面的例子一般Rust会只用一个字节来表示,但C会用一个int。
Rust还允许你指定enum的表示方法,用#[repr(i16)]可以让你定义一个等同于C++的这种枚举

  1. #include <stdint.h>
  2. enum git_error_code: int16_t {
  3. GIT_OK = 0,
  4. GIT_ERROR = -1,
  5. GIT_ENOTFOUND = -3,
  6. GIT_EEXISTS = -4,
  7. };

使用#[repr(C)]来表示和使用的例子

  1. enum tag {
  2. FLOAT = 0,
  3. INT = 1,
  4. };
  5. union number {
  6. float f;
  7. short i;
  8. };
  9. struct tagged_number {
  10. tag t;
  11. number n;
  12. };
  1. #[repr(C)]
  2. enum Tag {
  3. Float = 0,
  4. Int = 1
  5. }
  6. #[repr(C)]
  7. union FloatOrInt {
  8. f: f32,
  9. i: i32,
  10. }
  11. #[repr(C)]
  12. struct Value {
  13. tag: Tag,
  14. union: FloatOrInt
  15. }
  16. fn is_zero(v: Value) -> bool {
  17. use self::Tag::*;
  18. unsafe {
  19. match v {
  20. Value { tag: Int, union: FloatOrInt { i: 0 } } => true,
  21. Value { tag: Float, union: FloatOrInt { f: num } } => (num == 0.0),
  22. _ => false
  23. }
  24. }
  25. }

字符串

Rust和C的字符串之间有很多不同,C以null字符结尾,Rust存储了字符串的长度,而且能够吧null字符当一个元素存储。C字符串有可能包含错误的UTF-8字符。所以他们之间不能相互装换,因此Rust在std::ffi模块提供了对应的类型CString 和 CStr。

声明Foreign Functions和变量

函数

extern 代码段可以用来声明将要用到的C的函数和变量。extern中定义的内容都会被当做unsafe。

  1. use std::os::raw::c_char;
  2. extern {
  3. fn strlen(s: *const c_char) -> usize;
  4. }
  5. use std::ffi::CString;
  6. let rust_str = "I'll be back";
  7. let null_terminated = CString::new(rust_str).unwrap();
  8. unsafe {
  9. assert_eq!(strlen(null_terminated.as_ptr()), 12);
  10. }

CString::new(rust_str).unwrap()将一个Rust String转成CString。如果里面包含null会失败,所以返回的是Result。CString::new可以将任何实现了Into>的类型,会根据类型判断是否需要申请内存。
CStr的as_ptr 方法返回*const c_char。CString可以deref成Cstr。

变量

extern可以定义全局变量,environ是POSIX系统里的一个变量,保存环境参数。

  1. use std::ffi::CStr;
  2. use std::os::raw::c_char;
  3. extern {
  4. static environ: *mut *mut c_char;
  5. }
  6. unsafe {
  7. if !environ.is_null() && !(*environ).is_null() {
  8. let var = CStr::from_ptr(*environ);
  9. println!("first environment variable: {}",
  10. var.to_string_lossy())
  11. }
  12. }

CStr::from_ptr从指针生成一个CStr指向这个字符串。to_string_lossy返回一个Cow,如果给的字符串是合法的,返回一个&str,如果有无效的utf-8则生成一个新的字符串,无效的UTF8用代替。

外部库

放一个#[link(name = “git2”)]在extern上面,告诉编译器,下面的函数是从这个库来的。

动态连接

在编译的时候需要指定库的位置,可以通过床架build script的方法:再项目跟目录,也就是和Cargo.toml同级,放一个build.rs,这个代码只需要打印出需要的连接参数。

  1. fn main() {
  2. println!(r"cargo:rustc-link-search=native=/home/jimb/libgit2-0.25.1/build");
  3. }

运行的时候也需要把依赖的库放到LD_LIBRARY_PATH (Linux)或者PATH(windows)

  1. export LD_LIBRARY_PATH=/home/jimb/libgit2-0.25.1/build:$LD_LIBRARY_PATH
  1. set PATH=C:\Users\JimB\libgit2-0.25.1\build\Debug;%PATH%

静态连接

也可以将依赖一起打包到rlib中。

rust的习惯是提供访问C库的crate一般被命名为LIB-sys,LIB是C库的名。一个LIB-sys库一般都是将C的库静态连接到库里。

额外的方法参考cargo文档

声明外部库的所有函数和变量是很麻烦的可以用bindgen,在build script里创建自动生成的方法

内存管理

从外部函数返回的指针指向外部函数内所创建和拥有的值。这些变量的生命周期由外部函数自己处理,而且它所指的内容很有可能被下一个调用所改变。

  1. use std::ffi::CStr;
  2. use std::os::raw::c_int;
  3. fn check(activity: &'static str, status: c_int) -> c_int {
  4. if status < 0 {
  5. unsafe {
  6. let error = &*raw::giterr_last();
  7. println!("error while {}: {} ({})",
  8. activity,
  9. CStr::from_ptr(error.message).to_string_lossy(),
  10. error.klass);
  11. std::process::exit(1);
  12. }
  13. }
  14. status
  15. }
  1. unsafe fn show_commit(commit: *const raw::git_commit) {
  2. let author = raw::git_commit_author(commit);
  3. let name = CStr::from_ptr((*author).name).to_string_lossy();
  4. let email = CStr::from_ptr((*author).email).to_string_lossy();
  5. println!("{} <{}>\n", name, email);
  6. let message = raw::git_commit_message(commit);
  7. println!("{}", CStr::from_ptr(message).to_string_lossy());
  8. }

通过一个指针从外部函数获取的某个值,这个值的所有权是属于那个指针所指向的变量的。我们不用free它,但也不能持有这个获取的值超过原指针的生命周期,否则可能会变成悬垂指针。

也可以从外部函数获取所有权,git2通过其类型为指向指针的指针的第一个参数把所有权传递给调用者。

  1. let mut repo = ptr::null_mut();
  2. check("opening repository",
  3. raw::git_repository_open(&mut repo, path.as_ptr()));

另外一种方式是在Rust先创建一个未初始化的值,但Rust不允许未初始化的值,所以用一个特殊的方法mem::MaybeUninit::uninit(),返回一个MaybeUninit,告诉编译器分配一个给类型T用的内存,但不用初始化。这个内存是属于MaybeUninit的,编译器就不会去试图优化它,否则可能造成UB。MaybeUninit 提供一个as_mut_ptr()返回一个*mut T可以传递给外部函数去初始化它。如果我们想用一个未初始化的变量的时候,使用这种方法可以避免先初始化再被重写的麻烦。

  1. let oid = {
  2. let mut oid = mem::MaybeUninit::uninit();
  3. check("looking up HEAD",
  4. raw::git_reference_name_to_id(oid.as_mut_ptr(), repo, c_name));
  5. oid.assume_init()
  6. };

实用工具

std::sync::Once可以用来做初始化, ONCE.call_once只可以被调用一次,执行过后,再有任何线程来调用都会直接返回。它是原子的,而且调用很cheap。

libc.atexit可以在结束时调用一个函数指针,c的函数指针和Rust的函数指针不同,但可以直接用定义在extern里的函数。因为panic跨越语言边界是UB,所以要保证atexit调用的函数不panic。POSIX 禁止在atexit再调用exit,但可以用std::process::abort直接放弃进程。

在使用phantomData的时候我们只用它来存生命周期参数,在函数调用的时候也可以使用一个参数来只用作限制生命周期。

  1. /// Try to borrow a `&str` from `ptr`, given that `ptr` may be null or
  2. /// refer to ill-formed UTF-8. Give the result a lifetime as if it were
  3. /// borrowed from `_owner`.
  4. ///
  5. /// Safety: if `ptr` is non-null, it must point to a null-terminated C
  6. /// string that is safe to access for at least as long as the lifetime of
  7. /// `_owner`.
  8. unsafe fn char_ptr_to_str<T>(_owner: &T, ptr: *const c_char) -> Option<&str> {
  9. if ptr.is_null() {
  10. return None;
  11. } else {
  12. CStr::from_ptr(ptr).to_str().ok()
  13. }
  14. }

因为值是从raw pointer返回的,他是没有生命周期的,所以我们应该手动给他加上。_owner没有用到,但它的生命周期会隐式的赋给返回值。如果把上面的函数定义写完整是:

  1. fn char_ptr_to_str<'o, T: 'o>(_owner: &'o T, ptr: *const c_char) -> Option<&'o str>