8次发布
0.1.7 | 2022年8月16日 |
---|---|
0.1.6 | 2021年5月17日 |
0.1.5 | 2021年2月12日 |
0.1.4 | 2020年11月30日 |
#287 in FFI
8,112 每月下载量
用于 10 crates
110KB
345 代码行
inline-c
inline-c
是一个小型的Crate,允许用户在Rust中编写C(包括C++)代码。这两个环境都是严格沙箱化的:值跨越边界的可能性不明显。C代码被转换成一个字符串,然后写入临时文件。然后该文件被编译成目标文件,最终执行。可以运行关于C程序执行的断言。
inline-c
的主要目标是简化Rust程序C API的测试(例如,使用 cbindgen
生成)。请注意,它不仅仅与Rust程序绑定,它只是其存在的初始原因。
安装
将以下行添加到您的 Cargo.toml
文件中
[dev-dependencies]
inline-c = "0.1"
文档
assert_c
和 assert_cxx
宏位于 inline-c-macro
Crate中,但为了简单起见,在此Crate中重新导出。
能够在Rust中直接编写C代码提供了很好的机会,例如,在Rust文档中包含可执行的C示例,从而可以进行测试(使用 cargo test --doc
)。让我们看看一些例子。
基本用法
以下示例非常基础:C在标准输出上打印 Hello, World!
,Rust断言这一点。
use inline_c::assert_c;
fn test_stdout() {
(assert_c! {
#include <stdio.h>
int main() {
printf("Hello, World!");
return 0;
}
})
.success()
.stdout("Hello, World!");
}
或使用C++程序
use inline_c::assert_cxx;
fn test_cxx() {
(assert_cxx! {
#include <iostream>
using namespace std;
int main() {
cout << "Hello, World!";
return 0;
}
})
.success()
.stdout("Hello, World!");
}
assert_c
和 assert_cxx
宏返回一个 Result<Assert, Box<dyn Error>>
。有关可能的断言的更多信息,请参阅 Assert
。
以下示例测试返回值
use inline_c::assert_c;
fn test_result() {
(assert_c! {
int main() {
int x = 1;
int y = 2;
return x + y;
}
})
.failure()
.code(3);
}
环境变量
可以为给定C程序的执行定义环境变量。语法是使用特殊的#inline_c_rs
C指令,其语法如下
#inline_c_rs <variable_name>: "<variable_value>"
请注意变量值周围的双引号。
use inline_c::assert_c;
fn test_environment_variable() {
(assert_c! {
#inline_c_rs FOO: "bar baz qux"
#include <stdio.h>
#include <stdlib.h>
int main() {
const char* foo = getenv("FOO");
if (NULL == foo) {
return 1;
}
printf("FOO is set to `%s`", foo);
return 0;
}
})
.success()
.stdout("FOO is set to `bar baz qux`");
}
元环境变量
如果需要重复定义相同的环境变量,使用#inline_c_rs
C指令可能会变得繁琐。这就是为什么存在元环境变量的原因。它们具有以下语法
INLINE_C_RS_<variable_name>=<variable_value>
通常最好在一个build.rs
脚本中定义它们,例如。让我们用一个微小的例子来看看它是如何工作的
use inline_c::assert_c;
use std::env::{set_var, remove_var};
fn test_meta_environment_variable() {
set_var("INLINE_C_RS_FOO", "bar baz qux");
(assert_c! {
#include <stdio.h>
#include <stdlib.h>
int main() {
const char* foo = getenv("FOO");
if (NULL == foo) {
return 1;
}
printf("FOO is set to `%s`", foo);
return 0;
}
})
.success()
.stdout("FOO is set to `bar baz qux`");
remove_var("INLINE_C_RS_FOO");
}
CFLAGS
、CPPFLAGS
、CXXFLAGS
和LDFLAGS
一些经典的Makefile
变量如CFLAGS
、CPPFLAGS
、CXXFLAGS
和LDFLAGS
被inline-c
所理解,因此有特殊处理。它们的值在将C代码编译并链接到目标文件时添加到相应的编译器中。
提示:假设我们有一个名为foo
的Rust包,并且它导出了一个C API。我们可以定义CFLAGS
和LDFLAGS
如下,以通过在build.rs
脚本中写入以下内容来正确编译和链接所有C代码到Rust的libfoo
共享对象(假设libfoo
位于target/<profile>/
目录中,并且foo.h
位于根目录中)
use std::{env, path::PathBuf};
fn main() {
let include_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let mut shared_object_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
shared_object_dir.push("target");
shared_object_dir.push(env::var("PROFILE").unwrap());
let shared_object_dir = shared_object_dir.as_path().to_string_lossy();
// The following options mean:
//
// * `-I`, add `include_dir` to include search path,
// * `-L`, add `shared_object_dir` to library search path,
// * `-D_DEBUG`, enable debug mode to enable `assert.h`.
println!(
"cargo:rustc-env=INLINE_C_RS_CFLAGS=-I{I} -L{L} -D_DEBUG",
I = include_dir,
L = shared_object_dir.clone(),
);
// Here, we pass the fullpath to the shared object with
// `LDFLAGS`.
println!(
"cargo:rustc-env=INLINE_C_RS_LDFLAGS={shared_object_dir}/{lib}",
shared_object_dir = shared_object_dir,
lib = if cfg!(target_os = "windows") {
"foo.dll".to_string()
} else if cfg!(target_os = "macos") {
"libfoo.dylib".to_string()
} else {
"libfoo.so".to_string()
}
);
}
太棒了!现在运行cargo build --release
(生成共享对象)然后运行cargo test --release
来查看它的作用。
在Rust文档中使用inline-c
由于现在可以在Rust中编写C代码,因此可以相应地编写C示例,这些示例是
- 作为Rust文档的一部分,使用
cargo doc
,并且 - 与其他Rust示例一起使用
cargo test --doc
进行测试。
是的。使用cargo test --doc
测试C代码。这有多有趣?不需要任何技巧。可以写
/// Blah blah blah.
///
/// # Example
///
/// ```rust
/// # use inline_c::assert_c;
/// #
/// # fn main() {
/// # (assert_c! {
/// #include <stdio.h>
///
/// int main() {
/// printf("Hello, World!");
///
/// return 0;
/// }
/// # })
/// # .success()
/// # .stdout("Hello, World!");
/// # }
/// ```
pub extern "C" fn some_function() {}
这将编译成类似下面的内容
int main() {
printf("Hello, World!");
return 0;
}
请注意,上面的示例实际上是Rust代码,其中包含C代码。由于rustdoc
的#
技巧,只打印了C代码,但这是一个有效的Rust示例,并且已完全测试!
但是有一个小的限制:高亮显示。应用的是Rust的规则集,而不是C的规则集。请参阅此关于rustdoc
的问题以跟踪修复。
C 宏
C 宏通过 #define
指令仅支持 Rust nightly 版本。可以这样写:
use inline_c::assert_c;
fn test_c_macro() {
(assert_c! {
#define sum(a, b) ((a) + (b))
int main() {
return !(sum(1, 2) == 3);
}
})
.success();
}
注意,多行宏不起作用!这是因为 Rust 词法分析器消耗了 \
符号。最好的解决方案是将宏定义在另一个 .h
文件中,并使用 #include
指令包含它。
谁在使用它?
- Wasmer,领先的 WebAssembly 运行时,
- Cargo C,用于构建和安装与 C 兼容的库;当使用
cargo ctest
时,它为您配置inline-c
! - Biscuit,一个授权令牌微服务架构。
许可证
BSD-3-Clause
,见 LICENSE.md
。
依赖项
~5–15MB
~196K SLoC