任何事情都是相对的,就像Rust给咱们的印象一直是安全、快速,但实际上,彻底的安全是不可能实现的。所以,Rust中也是会有不安全的代码的。 编程
严格来说,Rust语言能够分为Safe Rust和Unsafe Rust。Unsafe Rust是Safe Rust的超集。在Unsafe Rust中并不会禁用任何的安全检查,Unsafe Rust出现的缘由是为了让开发者能够作一些更加底层的操做。这些事情自己也是不安全的,若是仍然要进行Rust的安全检查,那么就没法进行这些操做。安全
在进行下面这5种操做时,Unsafe Rust不会进行安全检查。多线程
Unsafe Rust的关键字是unsafe,它能够用来修饰函数、方法和trait,也能够用来标记代码块。函数
标准库中也有很多函数是unsafe的。例如String中的from_utf8_unchecked()
函数。它的定义以下:spa
pub unsafe fn from_utf8_unchecked(bytes: Vec<u8>) -> String { String { vec: bytes } }
这个函数被标记为unsafe的缘由是函数并无检查传入参数是不是合法的UTF-8序列。也就是提醒使用者注意,使用这个函数要本身保证参数的合法性。线程
用unsafe标记的trait也比较常见,在前面咱们见过的Send和Sync都是unsafe的trait。它们被用来保证线程安全, 将其标记为unsafe是告诉开发者,若是本身实现这两个trait,那么代码就会有安全风险。指针
咱们在调用unsafe函数或方法时,须要使用unsafe代码块。code
fn main() { let sparkle_heart = vec![240, 159, 146, 150]; let sparkle_heart = unsafe { String::from_utf8_unchecked(sparkle_heart) }; assert_eq!("💖", sparkle_heart); }
在了解了unsafe的基础语法以后,咱们再来具体看看前面提到的5种操做。内存
Rust的原生指针分为两种:可变类型*mut T
和不可变类型*const T
。开发
与引用和智能指针不一样,原生指针具备如下特性:
由这些特性能够看出,原生指针并不受Rust那一套安全规则的限制,所以,解引用原生指针是一种不安全的操做。换句话说,咱们应该把这种操做放在unsafe代码块中。下面这段代码就展现了原生指针的第一条特性,以及如何解引用原生指针。
fn main() { let mut num = 5; let r1 = &num as *const i32; let r2 = &mut num as *mut i32; unsafe { println!("r1 is: {}", *r1); println!("r2 is: {}", *r2); } }
在Rust编程中,原生指针常被用做和C语言打交道,原生指针有一些特有的方法,例如能够用is_null()
来判断原生指针是不是空指针,用offset()
来获取指定偏移量的内存地址的内容,使用read()/write()
方法来读写内存等。
调用unsafe的函数或方法必须放到unsafe代码块中,这点咱们在基础知识中已经介绍过。由于函数自己被标记为unsafe,也就意味着调用它可能存在风险。这点无需赘述。
对于不可变的静态变量,咱们访问它不会存在任何安全问题,可是对于可变的静态变量而言,若是咱们在多线程中都访问同一个变量,那么就会形成数据竞争。这固然也是一种不安全的操做。因此要放到unsafe代码块中,此时线程安全应由开发者本身来保证。
static mut COUNTER: u32 = 0; fn add_to_count(inc: u32) { unsafe { COUNTER += inc; } } fn main() { add_to_count(3); unsafe { println!("COUNTER: {}", COUNTER); } }
在这个例子中咱们没有使用多线程,这里只是想展现一下如何访问和修改可变静态变量。
当trait中包含一个或多个编译器没法验证其安全性的方法时,这个trait就必须被标记为unsafe。而想要实现unsafe的trait,首先在实现代码块的关键字impl
前也要加上unsafe标记。其次,没法被编译器验证安全性的方法,其安全性必须由开发者本身来保证。
前面咱们也提到了,常见的unsafe的trait有Send和Sync这两个。
Rust中的Union联合体和Enum类似。咱们可使用union关键字来定义一个联合体。
union MyUnion { i: i32, f: f32, } fn main() { let my_union = MyUnion{i: 3}; unsafe { println!("{}", my_union.i); } }
在初始化时,咱们每次只能指定一个字段的值。这就形成咱们在访问联合体中的字段时,有可能会访问到未定义的字段。所以,Rust让咱们把访问操做放到unsafe代码块中,以此来警示咱们必须本身保证程序的安全性。
本文咱们聊了Unsafe Rust的一些使用场景和使用方法。你只须要记住Unsafe的5种操做就好,在遇到这些操做时,必定要使用unsafe代码块。unsafe代码块不光是为了“骗”过编译器,要时刻提醒本身,unsafe代码块中的程序要由开发者本身保证其正确性。