高级特性
- 不安全rust
- 高级Trait
- 高级类型
- 高级函数和闭包
- 宏
不安全rust
隐藏这第二个语言,其未强制内存安全保证:Unsafe rust(不安全的rust);其和普通rust一样,但提供了额外的“超能力”。
unsafe rust存在的原因:
- 静态分析是保守的,使用unsafe rust,表示开发者知道自己在做什么,并承担相应风险。
- 计算机硬件本身就不安全,rust需要能够进行底层系统编程。
unsafe的超能力:
使用unsafe
关键字来切换到unsafe rust,开启一个块,块内为unsafe代码。
unsafe rust里可执行四个动作(超能力):
- 解引用原始指针
- 调用unsafe函数或方法
- 访问或修改可变的静态变量
- 实现unsafe trait
注意:
- unsafe并未关闭借用检查或停用其他安全检查,如果在里边使用引用,依然会被检查。所以即便在unsafe代码块中依然可获得一定的安全性。
- 任何内存安全相关的错误必须保留在unsafe块里。
- 尽可能隔离unsafe代码,最好将其封装在安全的抽象里,提供安全的API。
很多标准库里也含有unsafe代码,但使用安全接口封装,防止不安全代码泄露到调用处。
解引用原始指针
原始指针分类:
- 可变的:*mut T
- 不可变的:*constT。意味着指针在解引用后不能直接对其进行赋值
注意:这里的
*
不是解引用符号,它是类型名的一部分。
与引用的区别点,原始指针:
- 允许通过同时具有不可变和可变指针,或多个指向同一位置的可变指针,并忽略借用规则
- 无法保证能指向合理的内存
- 允许为 null
- 不实现任何自动清理
- 放弃保证的安全,换取更好的性能/与其它语言或硬件接口的能力
fn main() {
let mut n = 5;
let r1 = &n as *const i32; // 不可变原始指针
let r2 = &mut n as *mut i32; // 可变原始指针
// 可在非安全代码块外创建原始指针,但只能在不安全代码块内进行解引用
// 在非安全代码块内解引用这两个必定有效的指针
unsafe {
println!("r1 {}", *r1);
println!("r2 {}", *r2);
}
// 创建一个无法确定有效性的原始指针
let addr = 0x0123456usize; // 一个内存地址,可能有数据也可能无
let r = addr as *const i32; // 创建一个该地址的原始指针,编译不会报错
unsafe {
println!("r {}", *r); // 解引用不确定指针,编译不报错,需开发者自己保证安全性
}
}
/* 运行输出:
r1 5
r2 5
thread 'main' panicked at 'misaligned pointer dereference: address must be a multiple of 0x4 but is 0x123456', src\main.rs:15:7
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread caused non-unwinding panic. aborting.
*/
用原始指针的作用:与c语言进行接口,构建借用检查器无法理解的安全抽象。
调用unsafe函数或方法
unsafe函数或方法,在定义前加上unsafe关键字。
- 调用前需开发者确认满足要求或条件(通常由提供方提供文档说明),因为rust无法对这些条件进行验证。
- 需要在unsafe块里进行调用。
unsafe fn danger() {}
fn main() {
unsafe {
danger(); //正确调用
}
danger(); // 编译报错 call to unsafe function is unsafe and requires unsafe function or block
}
创建unsafe代码的安全抽象
函数包含unsafe代码并不意味着需要将整个函数标记为unsafe,将unsafe代码包裹在安全函数中是一个常见的抽象。
fn main() {
let mut v = vec![1, 2, 3, 4, 5];
let r = &mut v[..]; // 建立一个完整切片
// split_at_mut函数是标准库的函数,的作用是根据传入的数字切分vector,
// 分割成两个切片,分别是1 2 3, 4 5两组
let (a, b) = r.split_at_mut(3);
assert_eq!(a, &mut [1, 2, 3]);
assert_eq!(b, &mut [4, 5]);
}
split_at_mut是库函数,使用了不安全函数,为了理解,这里进行手动定义如下:
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..]) // 试图返回两个切片,以mid作为切分点
// 但这里会报无法对切片进行两次借用的错误,因为这是rust语言的规则
// 但实际情况是可以的切分借用的,因为这里借用的是前后两个无交叉部分,切分后无关联,但rust规则限定,无法识别这种情况
}
fn main() {
let mut v = vec![1, 2, 3, 4, 5];
let r = &mut v[..]; // 建立一个完整切片
// split_at_mut函数是标准库的函数,的作用是根据传入的数字切分vector,
// 分割成两个切片,分别是1 2 3, 4 5两组
let (a, b) = r.split_at_mut(3);
assert_eq!(a, &mut [1, 2, 3]);
assert_eq!(b, &mut [4, 5]);
}
编译报错:cannot borrow *slice
as mutable more than once at a time,所以这里需要使用unsafe代码块来实现,进行安全抽象,代码如下:
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = slice.len();
let ptr = slice.as_mut_ptr(); //返回原始指针,类型是*mut i32,指向slice
assert!(mid <= len);
unsafe {
(slice::from_raw_parts_mut(ptr, mid), // 创建切片
slice::from_raw_parts_mut(ptr.add(mid), len - mid))
} //由于使用了原始指针和偏移量,代码块不安全,调用需要在unsafe块里完成
// 虽然split_at_mut调用了不安全代码,但自身未标记为不安全函数,它就是不安全代码的安全抽象
}
fn main() {
let mut v = vec![1, 2, 3, 4, 5];
let r = &mut v[..]; // 建立一个完整切片
// split_at_mut函数是标准库的函数,的作用是根据传入的数字切分vector,
// 分割成两个切片,分别是1 2 3, 4 5两组
let (a, b) = r.split_at_mut(3);
assert_eq!(a, &mut [1, 2, 3]);
assert_eq!(b, &mut [4, 5]);
}
如果在main中不是调用安全抽象函数,而是直接调用非安全函数:
fn main() {
let addr = 0x0123456usize;
let r = addr as *const i32;
let slice: &[i32] = unsafe {
slice::from_raw_parts_mut(r, 50);
};
// 如果这种方式调用,编译不会报错,但运行时可能崩溃,因为无法保障切片是有效的
}
使用extern函数调用外部代码
extern关键字:简化创建和使用外部函数接口(FFI)的过程
外部函数接口(FFI,Foreign Function Interface):允许一种编程语言定义函数,并让其它编程语言能调用这些函数。
extern "C" {
fn abs(in: i32) -> i32;
}
fn main() {
unsafe {
println!("call c abs:{}", abs(-3));
}
}
应用二进制接口(ABI,Application Binary Interface):定义函数在汇编层的调用方式。
“C” ABI是最常见的ABI,它遵循C语言的ABI。
从其它语言调用rust函数
可使用extern创建接口,其它语言通过它们可调用rust的函数。
- 在fn前添加extern关键字,并指定ABI
- 还需添加
#[no_mangle]
注解(这是一个编译阶段),避免rust在编译时改变它的名称
#[no_mangle]
pub extern "C" fn call_func() {
println!("func in rust");
}
且无需使用unsafe。
访问或修改一个可变静态变量
rust支持全局变量,但因所有权机制可能产生某些问题,比如数据竞争。
在rust里,全局变量叫静态(static)变量。声明时必须指明类型。其生命周期能被编译器推断出,只存储'static
的引用。
static HELLO: &str = "hello";
fn main() {
println!("{}", HELLO);
}
静态变量
- 静态变量与常量类似
- 命名:SCREAMIN_SNAKE_CASE,全大写+下划线分割
- 必须标注类型
- 静态变量只能存储
'static
生命周期的引用,无需显示标注 - 访问不可变静态变量是安全的
常量和不可变静态变量的区别
- 静态变量有固定的内存地址,使用它的值总会访问同样的数据
- 常量允许使用它们的时候对数据进行复制
- 静态变量可以是可变的,访问和修改静态可变变量是不安全的(unsafe)