作为系统级编程语言,Rust的所有权机制和内存安全特性一直是其核心卖点。在实际开发中,引用和结构体是构建复杂系统的两大基石。今天我们就来深入探讨这两个特性在实际项目中的应用技巧。
Rust的引用分为可变引用(&mut T)和不可变引用(&T),这个设计完美解决了数据竞争问题。来看个实际场景:
rust复制fn process_data(data: &[i32]) -> i32 {
data.iter().sum()
}
fn modify_data(data: &mut Vec<i32>) {
data.push(42);
}
fn main() {
let mut numbers = vec![1, 2, 3];
let sum = process_data(&numbers);
modify_data(&mut numbers);
println!("Sum: {}, Modified: {:?}", sum, numbers);
}
这里有个关键细节:不可变引用可以有多个,但可变引用同时只能有一个。这个限制在并发编程中尤为重要,它让编译器能在编译期就发现数据竞争问题。
注意:当需要同时读写不同部分的数据时,可以使用
split_at_mut等方法安全地获取多个可变引用。
Rust的结构体比C/C++的更强大,来看个物联网设备建模的例子:
rust复制#[derive(Debug)]
struct IoTDevice {
id: String,
location: (f64, f64),
status: DeviceStatus,
last_update: std::time::SystemTime,
}
#[derive(Debug)]
enum DeviceStatus {
Online,
Offline,
Maintenance,
}
impl IoTDevice {
fn new(id: &str, lat: f64, lon: f64) -> Self {
Self {
id: id.to_string(),
location: (lat, lon),
status: DeviceStatus::Online,
last_update: std::time::SystemTime::now(),
}
}
fn update_location(&mut self, lat: f64, lon: f64) {
self.location = (lat, lon);
self.last_update = std::time::SystemTime::now();
}
}
结构体方法实现中,&self表示不可变借用,&mut self表示可变借用。这种显式标注让代码意图一目了然。
Rust标准库提供了跨平台的线程支持:
rust复制use std::thread;
use std::sync::{Arc, Mutex};
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
这里有几个关键点:
Arc(原子引用计数)用于线程间共享所有权Mutex提供内部可变性,确保线程安全访问move闭包获取变量的所有权实际经验:在Linux系统上,Rust线程默认使用pthread实现,Windows上则是使用原生线程API。
除了共享内存,Rust还支持通道(channel)进行消息传递:
rust复制use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from("hello");
tx.send(val).unwrap();
});
let received = rx.recv().unwrap();
println!("Got: {}", received);
}
通道有几种变体:
在Linux内核中,类似的概念是管道和消息队列,但Rust的通道更类型安全且没有竞态条件。
来看个实际例子:
rust复制use std::sync::{Mutex, Arc};
use std::thread;
struct Account {
balance: Mutex<i32>,
}
impl Account {
fn transfer(&self, target: &Account, amount: i32) {
// 确定锁定顺序:按ID排序
let (first, second) = if self as *const _ < target as *const _ {
(self, target)
} else {
(target, self)
};
let _lock1 = first.balance.lock().unwrap();
let _lock2 = second.balance.lock().unwrap();
*first.balance.lock().unwrap() -= amount;
*second.balance.lock().unwrap() += amount;
}
}
高并发场景下,锁争用会成为性能瓶颈。解决方案包括:
RwLock允许多个读取或单个写入thread_local!宏rust复制use std::sync::RwLock;
let data = RwLock::new(vec![1, 2, 3]);
// 多个读取者可以同时访问
{
let readers = vec![
thread::spawn(|| {
let d = data.read().unwrap();
println!("Reader 1: {:?}", d);
}),
thread::spawn(|| {
let d = data.read().unwrap();
println!("Reader 2: {:?}", d);
}),
];
for handle in readers {
handle.join().unwrap();
}
}
// 写入时需要独占访问
{
let writer = thread::spawn(|| {
let mut d = data.write().unwrap();
d.push(4);
});
writer.join().unwrap();
}
Rust提供了丰富的系统调用封装:
rust复制use std::fs::File;
use std::io::{self, Write};
use std::os::unix::fs::PermissionsExt;
fn create_secure_file(path: &str) -> io::Result<()> {
let mut file = File::create(path)?;
file.write_all(b"Sensitive data")?;
// 设置文件权限为600
let mut perms = file.metadata()?.permissions();
perms.set_mode(0o600);
file.set_permissions(perms)?;
Ok(())
}
在Linux系统上,这最终会调用open、write和chmod系统调用。Rust的std::fs模块提供了跨平台的抽象。
对于高性能IO,可以使用内存映射:
rust复制use memmap2::MmapOptions;
use std::fs::File;
fn process_large_file(path: &str) -> io::Result<()> {
let file = File::open(path)?;
let mmap = unsafe { MmapOptions::new().map(&file)? };
// 可以直接把mmap当作&[u8]使用
process_data(&mmap);
Ok(())
}
内存映射在数据库系统和高性能服务器中很常见,它避免了用户空间和内核空间之间的数据拷贝。
最后我们来实现一个简单的线程池,了解操作系统线程调度的底层逻辑:
rust复制use std::sync::{mpsc, Arc, Mutex};
use std::thread;
type Job = Box<dyn FnOnce() + Send + 'static>;
struct Worker {
id: usize,
thread: Option<thread::JoinHandle<()>>,
}
impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Self {
let thread = thread::spawn(move || loop {
let job = receiver.lock().unwrap().recv();
match job {
Ok(job) => {
println!("Worker {} got a job; executing.", id);
job();
}
Err(_) => {
println!("Worker {} shutting down.", id);
break;
}
}
});
Self {
id,
thread: Some(thread),
}
}
}
struct ThreadPool {
workers: Vec<Worker>,
sender: mpsc::Sender<Job>,
}
impl ThreadPool {
fn new(size: usize) -> Self {
assert!(size > 0);
let (sender, receiver) = mpsc::channel();
let receiver = Arc::new(Mutex::new(receiver));
let mut workers = Vec::with_capacity(size);
for id in 0..size {
workers.push(Worker::new(id, Arc::clone(&receiver)));
}
Self { workers, sender }
}
fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
let job = Box::new(f);
self.sender.send(job).unwrap();
}
}
impl Drop for ThreadPool {
fn drop(&mut self) {
for worker in &mut self.workers {
if let Some(thread) = worker.thread.take() {
thread.join().unwrap();
}
}
}
}
这个线程池实现展示了几个关键概念:
在实际操作系统中,线程调度要复杂得多,需要考虑优先级、CPU亲和性、负载均衡等因素。但我们的简单实现已经涵盖了基本概念。