快速搞懂Rust Send Trait

76 阅读4分钟

本文首发公众号 猩猩程序员 欢迎关注

什么是 Send Trait

Send 是 Rust 标准库中的一个重要的 marker trait,它标识那些可以在线程间安全传输的类型。

pub unsafe auto trait Send { }

核心特性

1. 自动实现 (Auto Trait)

Send 是一个 auto trait,这意味着编译器会自动为合适的类型实现它。如果一个类型的所有字段都实现了 Send,那么这个类型也会自动实现 Send。

2. Unsafe Trait

Send 被标记为 unsafe,这意味着手动实现 Send 需要使用 unsafe 代码,因为实现者需要保证类型确实可以安全地在线程间传输。

典型例子

Send 类型示例

use std::thread;
use std::sync::Arc;

fn main() {
    // i32 实现了 Send,可以在线程间传输
    let data = 42;
    let handle = thread::spawn(move || {
        println!("数据: {}", data);
    });
    handle.join().unwrap();
    
    // Arc<T> 实现了 Send(当 T: Send + Sync 时)
    let shared_data = Arc::new(vec![1, 2, 3, 4, 5]);
    let shared_data_clone = Arc::clone(&shared_data);
    
    let handle = thread::spawn(move || {
        println!("共享数据: {:?}", shared_data_clone);
    });
    handle.join().unwrap();
}

非 Send 类型示例

use std::rc::Rc;
use std::thread;

fn main() {
    let rc_data = Rc::new(42);
    
    // 以下代码会编译失败,因为 Rc<T> 没有实现 Send
    // let handle = thread::spawn(move || {
    //     println!("RC 数据: {}", rc_data);
    // });
    
    // 错误信息: `Rc<i32>` cannot be sent between threads safely
}

为什么 Rc 不是 Send?

正如官方文档所述,Rc<T> 不实现 Send 的原因是它使用非原子操作来管理引用计数。如果两个线程同时尝试克隆指向同一个引用计数值的 Rc,它们可能会同时尝试更新引用计数,这会导致未定义行为。

use std::rc::Rc;

// Rc 内部的引用计数操作不是原子的
struct RcExample {
    data: Rc<i32>,
}

// RcExample 不会自动实现 Send,因为 Rc<i32> 不是 Send

Arc vs Rc 的对比

use std::sync::Arc;
use std::rc::Rc;
use std::thread;

fn demonstrate_arc_vs_rc() {
    // Arc 使用原子操作,是 Send + Sync
    let arc_data = Arc::new(42);
    let arc_clone = Arc::clone(&arc_data);
    
    let handle = thread::spawn(move || {
        println!("Arc 数据: {}", arc_clone); // 这是安全的
    });
    handle.join().unwrap();
    
    // Rc 使用非原子操作,不是 Send
    let rc_data = Rc::new(42);
    // let rc_clone = Rc::clone(&rc_data);
    // thread::spawn(move || {
    //     println!("Rc 数据: {}", rc_clone); // 编译错误!
    // });
}

自定义类型的 Send 实现

自动实现 Send

// 所有字段都是 Send,所以这个结构体自动实现 Send
struct MyStruct {
    data: i32,
    text: String,
    numbers: Vec<u32>,
}

// 可以在线程间传输
fn use_my_struct() {
    let my_data = MyStruct {
        data: 42,
        text: "Hello".to_string(),
        numbers: vec![1, 2, 3],
    };
    
    std::thread::spawn(move || {
        println!("数据: {}, 文本: {}", my_data.data, my_data.text);
    });
}

包含非 Send 字段

use std::rc::Rc;

// 包含 Rc,所以不会自动实现 Send
struct NotSendStruct {
    data: i32,
    rc_data: Rc<String>,
}

// 以下代码会编译失败
// fn try_send_not_send() {
//     let not_send = NotSendStruct {
//         data: 42,
//         rc_data: Rc::new("Hello".to_string()),
//     };
//     
//     std::thread::spawn(move || {
//         println!("数据: {}", not_send.data); // 编译错误!
//     });
// }

手动实现 Send(需要 unsafe)

use std::marker::PhantomData;
use std::ptr::NonNull;

// 一个包含原始指针的结构体
struct MyRawPointer<T> {
    ptr: NonNull<T>,
    _marker: PhantomData<T>,
}

// 手动实现 Send(需要确保这是安全的)
unsafe impl<T> Send for MyRawPointer<T> where T: Send {}

// 注意:这需要非常小心,确保指针的使用是线程安全的

实际应用场景

1. 线程池中的任务

use std::thread;
use std::sync::mpsc;

fn worker_example() {
    let (sender, receiver) = mpsc::channel::<Box<dyn Fn() + Send>>();
    
    // 工作线程
    let handle = thread::spawn(move || {
        while let Ok(task) = receiver.recv() {
            task();
        }
    });
    
    // 发送任务(必须是 Send)
    let data = 42;
    sender.send(Box::new(move || {
        println!("处理数据: {}", data);
    })).unwrap();
    
    drop(sender);
    handle.join().unwrap();
}

2. 异步编程

use std::future::Future;
use std::pin::Pin;

// Future 必须是 Send 才能在不同线程间调度
fn async_example() -> Pin<Box<dyn Future<Output = i32> + Send>> {
    Box::pin(async {
        // 异步计算
        42
    })
}

粽结

Send trait 是 Rust 并发安全的核心组件之一:

  1. 自动实现:编译器会为合适的类型自动实现 Send
  2. 线程安全保证:只有 Send 类型才能在线程间传输
  3. 与 Sync 配合:Send 和 Sync 一起构成了 Rust 的并发安全模型
  4. 编译时检查:违反 Send 约束会在编译时被捕获,避免运行时错误

Send trait 让 Rust 能够在编译时就保证线程间数据传输的安全性,这是 Rust 并发编程安全性的重要基础。

本文首发公众号 猩猩程序员 欢迎关注