裸指针
裸指针(raw pointer,又称原生指针) 在功能上跟引用类似,同时它也需要显式地注明可变性。但是又和引用有所不同,裸指针长这样:
*const T和*mut T,它们分别代表了不可变和可变。在裸指针*const T中,这里的*只是类型名称的一部分,并没有解引用的含义
特点
- 可以绕过 Rust 的借用规则,可以同时拥有一个数据的可变、不可变指针,甚至还能拥有多个可变的指针
- 并不能保证指向合法的内存
- 可以是
null - 没有实现任何自动的回收 (drop)
创建裸指针
基于引用创建裸指针
下面的代码基于值的引用同时创建了可变和不可变的裸指针:
let mut num = 5;
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
在这段代码中并没有 unsafe 的身影,原因在于:创建裸指针是安全的行为,而解引用裸指针才是不安全的行为 :
fn main() {
let mut num = 5;
let r1 = &num as *const i32;
unsafe {
println!("r1 is: {}", *r1);
}
}
基于内存地址创建裸指针
use std::{slice::from_raw_parts, str::from_utf8_unchecked};
// 获取字符串的内存地址和长度
fn get_memory_location() -> (usize, usize) {
let string = "Hello World!";
let pointer = string.as_ptr() as usize;
let length = string.len();
(pointer, length)
}
// 在指定的内存地址读取字符串
fn get_str_at_location(pointer: usize, length: usize) -> &'static str {
unsafe { from_utf8_unchecked(from_raw_parts(pointer as *const u8, length)) }
}
fn main() {
let (pointer, length) = get_memory_location();
let message = get_str_at_location(pointer, length);
println!(
"The {} bytes at 0x{:X} stored: {}",
length, pointer, message
);
// 如果大家想知道为何处理裸指针需要 `unsafe`,可以试着反注释以下代码
// let message = get_str_at_location(1000, 10);
}
基于智能指针创建裸指针
还有一种创建裸指针的方式,那就是基于智能指针来创建:
let a: Box<i32> = Box::new(10);
// 需要先解引用a
let b: *const i32 = &*a;
// 使用 into_raw 来创建
let c: *const i32 = Box::into_raw(a);
解引用
let a = 1;
let b: *const i32 = &a as *const i32;
let c: *const i32 = &a;
unsafe {
println!("{}", *c);
}
使用 * 可以对裸指针进行解引用,由于该指针的内存安全性并没有任何保证,因此我们需要使用 unsafe 来包裹解引用的逻辑
api文档
unsafe
unsafe存在的主要原因是Rust的静态检查太强了;Rust为了内存安全,所做的所有权、借用检查、生命周期等规则往往是普适性的,编译器在分析代码时,一些正确代码会因为编译器无法分析出它的所有正确性,结果将这段代码拒绝,导致编译错误- 另一个原因是计算机底层的一些硬件就是不安全的(比如操作
IO访问外设),这些操作编译器是无法保证内存安全的,如果Rust只允许做安全的操作,那就无法完成这些操作,所以需要unsafe
实现 unsafe trait
任何 trait 只要声明成 unsafe,它就是 unsafe trait。在实现 unsafe trait 时,也必须定义为 unsafe。
unsafe trait 是对 trait 的实现者的约束,它表示在实现该 trait 时要小心,要保证内存安全,所以实现时需要加上 unsafe 关键字
但是在调用
unsafe trait时,直接直接调用,不需要在unsafe块中调用,因为这里的安全已经被实现者保证了,毕竟如果实现者没保证,调用者也做不了什么来保证安全,就像使用Send / Sync trait一样
// 这是一个unsafe trait,实现这个 trait 的开发者要保证实现是内存安全的
unsafe trait Foo {
fn foo(&self);
}
struct Nonsense;
// 使用 unsafe
unsafe impl Foo for Nonsense {
fn foo(&self) {
println!("foo!");
}
}
fn main() {
let nonsense = Nonsense;
nonsense.foo();
}
调用 unsafe 函数
使用 unsafe 关键字声明的函数即为 unsafe 函数,一个普通的 trait 里可以包含 unsafe 函数。
unsafe fn : 是函数对调用者的约束,它告诉函数的调用者要正确使用该函数,如果乱使用会带来内存安全的问题,所以调用 unsafe fn 时,需要加 unsafe 块把它包裹起来,提醒别人注意这里有 unsafe 代码
另一种调用
unsafe函数的方法是定义unsafe fn,然后在这个unsafe fn里调用其它的unsafe fn
例如:
trait Bar {
// 普通的trait里包含 unsafe 函数,表示调用这个函数的人要保证调用是安全的
unsafe fn bar(&self);
}
struct Nonsense;
impl Bar for Nonsense {
unsafe fn bar(&self) {
println!("bar!");
}
}
fn main() {
let nonsense = Nonsense;
// 调用者需要为 安全 负责,使用unsafe block包裹起来
unsafe { nonsense.bar() };
}
解引用裸指针
裸指针的解引用操作是不安全的,有潜在风险,所以解引用时也需要使用 unsafe 来明确告诉编译器,也就是要使用 unsafe 块包裹起来
FFI
最后一种可以使用 unsafe 的地方是 FFI(Foreign Function Interface)。当 Rust 要使用其它语言的能力时(比如 C/C++ 的库),Rust 编译器并不能保证那些语言具备内存安全,所以和第三方语言交互的接口,一律要使用 unsafe。
例如,Rust 调用 libc 的 malloc / free 函数时要使用 unsafe 包裹
libc提供了与Rust支持的各平台上的最基础系统C库打交道的所有必要设施
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 包裹
unsafe { libc::free(transmute(data)) };
}
修改静态变脸
静态变量也可以使用mut来将其标注为可变,但是在修改的时候也只能在unsafe块中:
static mut COUNTER: u32= 0;
// COUNTER = 1; // 报错,使用或修改都需要unsafe块
unsafe {
COUNTER = 1;
println!("{}", COUNTER); // 1
}