Unsafe
unsafe trait
最常见的unsafe trait是Send/Sync
1 2
| pub unsafe auto trait Send {} pub unsafe auto trait Sync {}
|
任何trait,只要声明称unsafe,它就是一个unsafe trait.一个正常的trait也可以包含unsafe 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| unsafe trait Foo { fn foo(&self); }
trait Bar { unsafe fn bar(&self); }
struct Nonsense;
unsafe impl Foo for Nonsense { fn foo(&self) { println!("foo!"); } }
impl Bar for Nonsense { unsafe fn bar(&self) { println!("bar!"); } }
fn main() { let nonsense = Nonsense; nonsense.foo();
unsafe { nonsense.bar() }; }
|
unsafe trait是对trait的实现者的约束,它告诉trait的实现者:实现我的时候要小心,要保证内存安全,所以实现的时候需要加unsafe关键字.
但 unsafe trait 对于调用者来说,可以正常调用,不需要任何 unsafe block,因为这里的 safety 已经被实现者保证了,毕竟如果实现者没保证,调用者也做不了什么来保证 safety,就像我们使用 Send/Sync 一样。
而 unsafe fn 是函数对调用者的约束,它告诉函数的调用者:如果你胡乱使用我,会带来内存安全方面的问题,请妥善使用,所以调用 unsafe fn 时,需要加 unsafe block 提醒别人注意。
调用已有的unsafe函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| use std::collections::HashMap;
fn main() { let map = HashMap::new(); let mut map = explain("empty", map);
map.insert(String::from("a"), 1); explain("added 1", map); }
fn explain<K, V>(name: &str, map: HashMap<K, V>) -> HashMap<K, V> { let arr: [usize; 6] = unsafe { std::mem::transmute(map) }; println!( "{}: bucket_mask 0x{:x}, ctrl 0x{:x}, growth_left: {}, items: {}", name, arr[2], arr[3], arr[4], arr[5] );
unsafe { std::mem::transmute(arr) } }
|
对裸指针解引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| fn main() { let mut age = 18;
let r1 = &age as *const i32; let r2 = &mut age as *mut i32;
unsafe { println!("r1: {}, r2: {}", *r1, *r2); } }
fn immutable_mutable_cant_coexist() { let mut age = 18; let r1 = &age; let r2 = &mut age;
println!("r1: {}, r2: {}", *r1, *r2); }
|
使用裸指针,可变指针和不可变指针可以共存,不像可变引用和不可变引用无法共存。这是因为裸指针的任何对内存的操作,无论是 ptr::read / ptr::write,还是解引用,都是 unsafe 的操作,所以只要读写内存,裸指针的使用者就需要对内存安全负责。
不可信的内存地址,使用unsafe:
1 2 3 4 5 6 7 8 9 10 11 12
| fn main() { let r1 = 0xdeadbeef as *mut u32;
println!("so far so good!");
unsafe { *r1 += 1; println!("r1: {}", *r1); } }
|
使用FFI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| use std::mem::transmute;
fn main() { let data = unsafe { let p = libc::malloc(8); let arr: &mut [u8; 8] = transmute(p); arr };
data.copy_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8]);
println!("data: {:?}", data);
unsafe { libc::free(transmute(data)) }; }
|
不推荐使用unsafe的场景
访问或者修改可变静态变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| use std::thread;
static mut COUNTER: usize = 1;
fn main() { let t1 = thread::spawn(move || { unsafe { COUNTER += 10 }; });
let t2 = thread::spawn(move || { unsafe { COUNTER *= 10 }; });
t2.join().unwrap(); t1.join().unwrap();
unsafe { println!("COUNTER: {}", COUNTER) }; }
|
应该改进为AtomicXXX/或者Mutex/RwLock:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| use std::{ sync::atomic::{AtomicUsize, Ordering}, thread, };
static COUNTER: AtomicUsize = AtomicUsize::new(1);
fn main() { let t1 = thread::spawn(move || { COUNTER.fetch_add(10, Ordering::SeqCst); });
let t2 = thread::spawn(move || { COUNTER .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |v| Some(v * 10)) .unwrap(); });
t2.join().unwrap(); t1.join().unwrap();
println!("COUNTER: {}", COUNTER.load(Ordering::Relaxed)); }
|
在宏里使用unsafe
在宏中使用 unsafe,是非常危险的。
首先使用你的宏的开发者,可能压根不知道 unsafe 代码的存在;其次,含有 unsafe 代码的宏在被使用到的时候,相当于把 unsafe 代码注入到当前上下文中。在不知情的情况下,开发者到处调用这样的宏,会导致 unsafe 代码充斥在系统的各个角落,不好处理;最后,一旦 unsafe 代码出现问题,你可能都很难找到问题的根本原因。
使用unsafe提升性能
性能,是一个系统级的问题。在你没有解决好架构、设计、算法、网络、存储等其他问题时,就来抠某个函数的实现细节的性能,我认为是不妥的,尤其是试图通过使用 unsafe 代码,跳过一些检查来提升性能。
要知道,好的算法和不好的算法可以有数量级上的性能差异。而有些时候,即便你能够使用 unsafe 让局部性能达到最优,但作为一个整体看的时候,这个局部的优化可能根本没有意义。