Updated 'src/advanced_features/unsafe.md'.

This commit is contained in:
Hector PENG
2026-04-23 09:12:15 +08:00
parent 7e0e246c3e
commit 631e084c7c
5 changed files with 94 additions and 6 deletions

View File

@@ -0,0 +1,10 @@
[package]
name = "my_rust_lib"
version = "0.1.0"
edition = "2024"
[lib]
name = "my_rust_lib"
crate-type = ["cdylib"]
[dependencies]

View File

@@ -0,0 +1,9 @@
#include <stdio.h>
extern void call_from_c();
int main () {
call_from_c();
return 0;
}

BIN
projects/my_rust_lib/my_app Executable file

Binary file not shown.

View File

@@ -0,0 +1,5 @@
#[unsafe(no_mangle)]
pub extern "C" fn call_from_c() {
println!("刚刚从 C 调用了 Rust 函数!");
}

View File

@@ -32,7 +32,7 @@ Rust 具有不安全的另一个自我an unsafe alter ego的另一原因
在第 4 章的 [悬空引用](../ownership/references_and_borrowing.md#悬空引用) 小节中,我们提到编译器会确保引用始终有效。不安全 Rust 有两种名为 *原始指针* 的新类型,他们与引用类似。与引用一样,原始指针可以是不可变的或可变的,并分别写为 `*const T``*mut T`。其中星号 `*` 并非是解引用运算符;而是类型名字的一部分。在原始指针的语境下,*不可变* 意味着该指针在解引用后不能直接赋值。
不同于引用和灵巧指针,原始指针有着以下特征:
不同于引用和灵巧指针,原始指针有着以下特征:
- 允许通过同时存在指向同一内存位置的不可变和可变指针,或多个可变指针,来忽略借用规则;
- 不保证指向有效的内存;
@@ -234,7 +234,7 @@ fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
**清单 20-7**:从任意内存位置创建切片
> **:上面的代码运行结果:
> **译注**:上面的代码运行结果:
>
> ```console
> $ cargo run
@@ -283,7 +283,7 @@ fn main() {
**清单 20-8**:声明并调用在另一种语言中定义的 `extern` 函数
> 上面代码运行结果为:
> **译注**上面代码运行结果为:
>
> ```console
> $ cargo run
@@ -330,6 +330,70 @@ pub extern "C" fn call_from_c() {
`extern` 的这种语法,仅需在属性中要求 `unsafe`,而无需在 `extern` 代码块上声明。
> **译注**:以从 C 中调用上面的 `call_from_c` 为例,具体步骤及相应的 C 代码如下。
>
> 1. 使用命令 `cargo new my_rust_lib --lib` 创建一个库代码箱;
>
> 2. 更新这个库代码箱的 `Cargo.toml` 文件,加入下面的内容:
>
> ```toml
> [lib]
> name = "my_rust_lib"
> crate-type = ["cdylib"]
> ```
>
> 着一个 `lib` 小节指定了这个库代码像应被编译为一个兼容 C 的动态库。
>
> 3. 在 `src/lib.rs` 中,使用属性 `#[unsafe(no_mangle)]` 阻止 Rust 修改函数名字,使用 `extern "C"` 来应用 C 的调用约定。
>
> ```rust
> #[unsafe(no_mangle)]
> pub extern "C" fn call_from_c() {
> println!("刚刚从 C 调用了 Rust 函数!");
> }
> ```
>
> 4. 构建共享库
>
> 运行构建命令以生成共享对象文件例如Linux 上的 `.so`MacOS 上的 `.dylib` 或 Windown 上的 `.dll`)。
>
> ```console
> cargo build --release
> ```
>
> 输出将位于 `target/release/libmy_rust_lib.so`(或类似的扩展名)。、
>
> 5. 从 C 语言中调用
>
> 为了从 C 中调用该库,咱们需要一个头文件,或一个匹配 Rust 签名的函数原型。
>
> ```c
> #include <stdio.h>
>
> extern void call_from_c();
>
> int main () {
> call_from_c();
>
> return 0;
> }
> ```
>
> 6. 编译和链接
>
> 在编译 C 代码时,要将其与生成的 Rust 库链接。咱们可需需要设置 `LD_LIBRARY_PATH`(在 Linux 上),以便系统可以在运行时找到该共享对象。
>
> ```bash
> # 编译并链接(根据需要调整路径和库名)
> $ gcc main.c -L./target/release -lmy_rust_lib -o my_app
>
> # 运行程序
> $ export LD_LIBRARY_PATH=./target/release:$LD_LIBRARY_PATH
> $ ./my_app
> 刚刚从 C 调用了 Rust 函数!
> ```
>
> 专业提示:对于复杂的项目,请使用 [`cbindgen`](https://github.com/mozilla/cbindgen) 工具,从咱们的 Rust 代码自动生成 C/C++ 头文件。
## 访问或修改可变静态变量
@@ -360,7 +424,7 @@ fn main() {
```rust
static mut COUNTER: u32 = 0;
/// 安全提示:同时从多个线程调用此函数属于未定义行为,
/// SAFETY:同时从多个线程调用此函数属于未定义行为,
/// 因此咱们 *必须* 确保每次仅从单个线程调用他。
fn add_to_count(inc: u32) {
@@ -371,7 +435,7 @@ fn add_to_count(inc: u32) {
fn main() {
unsafe {
// 安全提示:此函数仅在 `main` 中的单个线程中调用。
// SAFETY:此函数仅在 `main` 中的单个线程中调用。
add_to_count(3);
println! ("COUNTER: {}", *(&raw const COUNTER));
}
@@ -382,7 +446,7 @@ fn main() {
与常规变量一样,我们使用 `mut` 关键字指定可变性。任何读写 `COUNTER` 的代码都必须位于 `unsafe` 代码块内。清单 20-11 中的代码会编译并如预期打印 `COUNT: 3` ,因为他是单线程的。让多个线程访问 `COUNTER` 可能会导致数据竞争,因此这属于为定义行为。因此,我们需要标记整个函数为 `unsafe` 并记录安全限制,以便任何调用该函数的人,都知道哪些是可以安全执行,哪些不可以安全执行。
每当我们编写不安全的函数时,惯例是在代码中添加以 `SAFETY` 开头的注释,并解释调用者需要采取哪些措施才能安全地调用该函数。同样,每当我们执行不安全的操作时,惯例也是添加以 `SAFETY` 开头的注释,说明安全规则是如何得到维护的。
每当我们编写不安全的函数时,惯例是在代码中添加以 `SAFETY`(安全提示) 开头的注释,并解释调用者需要采取哪些措施才能安全地调用该函数。同样,每当我们执行不安全的操作时,惯例也是添加以 `SAFETY` 开头的注释,说明安全规则是如何得到维护的。
此外,编译器会默认通过编译器 lint 规则阻止任何试图创建可变静态变量引用操作。您必须通过添加 #[allow(static_mut_refs)] 注解来显式禁用该 lint 规则的保护,或者通过使用原始借用运算符之一创建的原始指针来访问该可变静态变量。 这包括隐式创建引用的情况,例如本代码片段中在 `println!` 中的用法。要求通过原始指针创建对静态可变变量的引用,有助于使使用它们的安全要求更加明确。