全部教程·
Rust语言·
Rust高级编程
[目录]
·
push和pop
Rust 高级编程
Rust 非安全程序设计
Rust 安全代码
安全与非安全代码对比 安全与非安全代码交互 非安全Rust能做什么 编写非安全代码数据布局
repr(Rust) 类型中的奇行种 可选的数据表达方式所有权
所有权 引用 别名 生命周期 生命周期的局限 省略生命周期 无界生命周期 高阶trait边界 子类型和变性 Drop检查 幽灵数据 分解借用类型转换
类型转换 强制类型转换 点操作符 显式类型转换 变形未初始化内存
未初始化内存 安全方式 Drop标志 非安全方式资源管理
基于所有权的资源管理 构造函数 析构函数 泄露展开
展开 异常安全性 污染并发
并发 竞争 Send和Sync 原子操作实现 Vec
实现 Vec 布局 内存分配 push和pop 回收资源 DeRef 插入和删除 IntoIter RawVec Drain 处理零尺寸类型 最终代码FFI
FFI
Rust 高级编程
Rust 非安全程序设计
Rust 安全代码
安全与非安全代码对比 安全与非安全代码交互 非安全Rust能做什么 编写非安全代码数据布局
repr(Rust) 类型中的奇行种 可选的数据表达方式所有权
所有权 引用 别名 生命周期 生命周期的局限 省略生命周期 无界生命周期 高阶trait边界 子类型和变性 Drop检查 幽灵数据 分解借用类型转换
类型转换 强制类型转换 点操作符 显式类型转换 变形未初始化内存
未初始化内存 安全方式 Drop标志 非安全方式资源管理
基于所有权的资源管理 构造函数 析构函数 泄露展开
展开 异常安全性 污染并发
并发 竞争 Send和Sync 原子操作实现 Vec
实现 Vec 布局 内存分配 push和pop 回收资源 DeRef 插入和删除 IntoIter RawVec Drain 处理零尺寸类型 最终代码FFI
FFIRust push和pop
我们从push开始,实现一些真正的功能。它要做的事情就是检查空间是否已满,满了就扩容,然后写数据到下一个索引位置,最后增加长度。
写数据时,我们一定要小心,不要计算我们要写入的内存位置的值。最坏的情况,那块内存是一块未初始化的内存。最好的情况是那里存着我们已经pop出去的值。不管哪种情况,我们都不能直接索引这块内存然后解引用它,因为这样其实是把内存中的值当做了一个合法的T的实例。更糟糕的是,foo[idx] = x会调用foo[idx]处旧有值的drop方法!
正确的方法是使用ptr::write,它直接用值的二进制覆盖目标地址,不会计算任何的值。
对于push,如果原有的长度(调用push之前的长度)为0,那么我们就要写到第0个索引位置。所以我们应该用原有的长度做offset。
pub fn push(&mut self, elem: T) {
if self.len == self.cap { self.grow(); }
unsafe {
ptr::write(self.ptr.offset(self.len as isize), elem);
}
// 这一句不会失败,而会首先OOM
self.len += 1;
}
pop是什么样的呢?尽管现在我们要访问的索引位置已经初始化了,Rust不允许我们用解引用的方式将值移出,因为那样的话整个内存都会回到未初始化状态!这时我们需要用ptr:read,它从目标位置拷贝出二进制值,然后解析成类型T的值。这时原有位置处的内存逻辑上是未初始化的,可实际上那里还是存在这一个正常的T的实例。
对于pop,如果原有长度是1,我们要读的是第0个索引位置。所以我们应该是按新的长度做offset。
pub fn pop(&mut self) -> Option<T> {
if self.len == 0 {
None
} else {
self.len -= 1;
unsafe {
Some(ptr::read(self.ptr.offset(self.len as isize)))
}
}
}
下一章:Rust 回收资源
我们应该实现Drop,否则就要造成大量的资源泄露了。最简单的方法是循环调用pop直到产生None为止,然后再回收我们的缓存。注意,当T: !Drop的时候,调用pop不是必须的。理论上我们可以问一问RustT是不是nee ...
AI 中文社