不安全RUST
的世界里拥有两种类似于引用的指针类型,它们都被叫做裸指针(raw pointer
)。与引用类似,裸指针要么是可变的,要么是不可变的,它们分别被写作*const
和*mut T
。这里的星号是类型名的一部分而不是解引用操作。
如何从一个引用中同时创建出不可变的和可变的裸指针:
fn main() {
let mut num = 5;
let r1 = &mut num as *const i32;
let r2 = &mut num as *mut i32;
}
我们没有在这段代码中使用unsafe
关键字。你可以在安全代码内安全合法地创建裸指针,但不能在不安全代码块外解引用裸指针。
在创建裸指针的过程中,我们使用了as
来分别将不可变引用和可变引用强制转换为了对应的裸指针类型。由于这两个裸指针来自有效的引用,所以我们能够确认它的有效性。
为了使用*
解引用指针,我们需要添加一个unsafe
块。创建一个指针并不会产生任何危害,只有当我们视图访问它指向的值时才可能因为无效的值而导致程序异常。
fn main() {
let mut num = 5;
let r1 = &mut num as *const i32;
let r2 = &mut num as *mut i32;
unsafe {
println!("r1 is: {}", *r1);
println!("r2 is: {}", *r2);
}
}
如果我们尝试同时创建一个指向num
的可变引用和不可变引用,那个就会因为RUST
所有权规则而导致编译失败。但在使用裸指针时,我们却可以创建指向同一地址的可变指针和不可变指针,并通过可变指针来改变数据。
函数中包含不安全代码并不意味着我们需要将整个函数标记为不安全。实际上,将不安全的代码封装在安全的函数中是一种十分常见的抽象。
fn main() {
let mut v = vec![1, 2, 3, 4, 5, 6];
let r = &mut v[..];
let (a, b) = r.split_at_mut(3);
assert_eq!(a, &mut [1, 2, 3]);
assert_eq!(b, &mut [4, 5, 6]);
}
例子中split_at_mut
接收一个切片并从给定的索引参数处将其分割成两个切片。我们无法仅仅使用安全RUST
来实现这个函数,下面展示了一个可能的尝试,但它却无法通过编译。
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = slice.len();
assert!(mid <= len);
(&mut slice[..mid], &mut slice[mid..])
}
借用一个切片的不同部分从原理上来讲是没有问题的,因为两个切片没有交叉的地方,但RUST
并没有足够智能到理解这些信息。当我们能够确定某段代码的正确性而RUST
却不能时,不安全代码就可以登场了。
下面使用unsafe
代码块、裸指针及不安全的代码来实现split_at_mut
:
fn main() {
let mut v = vec![1, 2, 3, 4, 5, 6];
let slice = &mut v;
let (s1, s2) = split_at_mut(slice, 6);
println!("s1:{:?}", s1);
println!("s2:{:?}", s2);
}
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = slice.len();
let ptr = slice.as_mut_ptr();
assert!(mid <= len);
unsafe {
(
slice::from_raw_parts_mut(ptr, mid),
slice::from_raw_parts_mut(ptr.offset(mid as isize), len - mid),
)
}
}
我们使用as_mut_ptr
方法来获取切片的裸指针,由于我们使用使用了可变的i32
类型的切片,所以as_mut_ptr
会返回一个类型为*mut i32
的裸指针。
slice::from_raw_parts_mut
函数接收一个裸指针和长度来创建切片,函数从ptr
处创建了一个拥有mid
个元素的切片,接着我们又在ptr
上使用mid
作为偏移量参数调用offset
方法得到一个从mid
处开始的裸指针,并基于它创建一个起始于mid
处且拥有剩余元素的切片。
由于函数slice::from_raw_parts_mut
默认接收的裸指针参数是合法的,所以它是不安全的。裸指针的offset
方法也是不安全的,因为它必须默认地址的偏移量也是一个有效的指针。因此,我们必须在unsafe
块中调用这两个函数。
关于mid == len
的情况有点让人误解,RUST
中slice[slice.len()]
会返回一个空的切片,所以长度值也是有效的。