Rust笔记 - unsafe

1,253 阅读3分钟

unsafe啥东西啊!Rust不是号称是safe的吗?怎么还有一个unsafe关键字的呢?那Rust还safe不safe呢?一连串的黑人问号。查了一些资料,妈耶,Rust标准库中到处都在使用unsafe关键字对函数进行包装,保证提供的APIs是safe的。

为什么会有unsafe关键字呢?因为Rust编译器"无能",没有办法追踪所有的情况,特别在使用裸指针的时候。所以,需要使用unsafe关键字标明,一方面是为了告诉编译器,这部分内容你没办法追踪,我们自己看着办。另一方面是为了告诉读/写代码的人,编译器没办法保证unsafe代码安全,你自己看着办,爱用不用。那写unsafe代码的人是不是很不负责任?大量代码使用unsafe是否会变得像C/C++一样,裸指针到处乱跑?程序容易崩溃?

Rust社区中有一群人严重抵触使用unsafe代码,将unsafe代码看作是严重的危害。产生这样的情况还不是当初Rust将安全最为核心买点,搞出一个unsafe关键字,某些人心里难免会存在落差。既然unsafe作为一个功能,肯定有其存在的道理。而且标准库中到处可见unsafe代码,可见unsafe功能对于Rust来说还是相当重要的。

unsafe代码真的有这么让人厌恶吗?大多数人使用Rust都是奔着安全和高效去的,可能已经受够了C++代码?如果代码大量使用unsafe难免会失落。的确,我也是这么想的。在使用unsafe代码时非常克制,能不用就不用,但unsafe代码是必须的。为什么呢?计算机从来没有银弹,Rust也不会是。一个安全的东西一开始难道不是构建在不安全的东西上面的吗?想想没有操作系统时,各个程序跑在裸板上,内存读写不受控制,程序经常意外奔溃。而操作系统通过虚拟内存机制,让各个程序能互不干扰(安全)的运行。为什么操作系统能让各个程序安全运行呢?因为操作系统handle了一切,将无序混乱的东西安排的井然有序。Rust的安全也是构建在不安全之上的,通过合理的机制安排代码的构建,形成安全的API,供外部调用。那么说C/C++也能写出安全的代码?那是肯定的,只要有一套合理的机制安排代码,完善的错误检查,同样能写出安全的代码?但C/C++难写出安全的代码,问题不仅在语言本身,更在于使用语言的人,人总是会犯错误的。而Rust的编译器能帮助我们提前检查出可能错误的代码,但Rust的编译器并没有强大到能handle一切可能错误的情况。其中unsafe代码就不受编译器监视。我想说什么?unsafe代码是不可避免的,问题的关键不是完全避免使用unsafe,而是如何写出安全的unsafe代码


下面的Rust例子出自《Programming Rust: Fast, Safe, Systems Development》:

mod ref_with_flag {
    use std::marker::PhantomData;
    use std::mem::align_of;

    pub struct RefWithFlag<'a, T: 'a> {
        ptr_and_bit: usize,
        behaves_like: PhantomData<&'a T>, // 不占空间,用于获取生命期,保证引用的生命期不会超出实体本身。
    }
    impl<'a, T: 'a> RefWithFlag<'a, T> {
        pub fn new(ptr: &'a T, flag: bool) -> RefWithFlag<T> {
            assert!(align_of::<T>() % 2 == 0);
            RefWithFlag {
                ptr_and_bit: ptr as *const T as usize | flag as usize,
                behaves_like: PhantomData,
            }
        }
        pub fn get_ref(&self) -> &'a T {
            unsafe {
                let ptr = (self.ptr_and_bit & !1) as *const T;
                &*ptr
            }
        }
        pub fn get_flag(&self) -> bool {
            self.ptr_and_bit & 1 != 0
        }
    }
}

fn main() {
    let vec = vec![10, 20, 30];
    let flagged = ref_with_flag::RefWithFlag::new(&vec, true);
    assert_eq!(flagged.get_ref()[1], 20);
    assert_eq!(flagged.get_flag(), true);
}

上面的例子展示了如何使用unsafe代码写出安全API。程序的功能主要是:将Flag标志位保存在指针的最低位。