到目前为止,我只用 Rust 编写了安全代码。现在我正在从头开始编写一个 DBMS,以便深入理解该语言。由于性能是这里的重中之重,所以我尝试自己编写大部分代码,并且偶然发现了不安全 Rust 中的 UB 问题。
我想实现页面缓存功能,但由于所有权规则,无法从文件中获取页面,将其存储在缓存中,然后返回对其的引用。这是 BTree 管理器结构体的缓存方法:
核心.rs
fn load_page(&self, page_number: u32) -> Result<&mut Page, Box<dyn Error>> {
let page = match self.load_from_cache(page_number) {
None => self.load_from_file(page_number)?,
Some(page) => return Ok(page),
};
Ok(unsafe { (*self.cache.get()).insert(page) })
}
fn load_from_cache(&self, page_number: u32) -> Option<&mut Page> {
unsafe { (*self.cache.get()).find_mut(page_number) }
}
缓存.rs
pub fn find_mut(&mut self, page_number: u32) -> Option<&mut Page> {
self.find_page(page_number)
.and_then(|index| self.cache[index].as_mut())
}
fn find_page(&self, page_number: u32) -> Option<usize> {
let mut begin = 0usize;
let mut end = self.cache.len();
let mut middle = (begin + end) / 2;
while begin != end {
match self.cache[middle] {
None => begin = middle + 1,
Some(ref page) => {
if page.page_number() > page_number {
begin = middle + 1;
} else if page.page_number() < page_number {
end = middle - 1;
} else {
return Some(middle);
}
}
}
middle = (begin + end) / 2
}
None
}
pub fn insert(&mut self, page: Page) -> &mut Page {
if self.size < self.cache.len() {
self.upheap_and_get(page)
} else {
let to_discard = self.longest_unused.unwrap();
self.cache[to_discard..].rotate_left(1);
self.upheap_and_get(page)
}
}
如果页面未缓存,则从文件加载:
核心.rs
fn load_from_file(&self, page_number: u32) -> Result<Page, Box<dyn Error>> {
let mut buffer = match self.file.find_page(page_number) {
None => return Err(todo!()),
Some(buffer) => buffer,
};
let page = Page::deserialize(&mut buffer);
Ok(page)
}
文件.rs
pub fn find_page(&self, page_number: u32) -> Option<&[u8]> {
match self.load_page(page_number + 4096) {
None => None,
Some(slice) => Some(slice),
}
}
pub fn find_page_mut(&mut self, page_number: u32) -> Option<&mut [u8]> {
self.load_page(page_number + 4096)
}
fn load_page(&self, page_number: u32) -> Option<&mut [u8]> {
let total_length = unsafe { (*self.buffer.get()).len() };
let page_offset = PAGE_SIZE * page_number as usize;
let page_end = page_offset + PAGE_SIZE;
if page_end <= total_length {
unsafe { Some(&mut (*self.buffer.get())[page_offset..page_end]) }
} else {
None
}
}
这是项目中我选择退出所有权规则的唯一两个地方,其中
buffer
属于 UnsafeCell<MemmapMut>
类型。我已经采取了所有预防措施来确保 buffer
永远不会悬空,并且我或多或少相信所有使用返回值的方法都遵循所有权规则,因为返回的页面是存储在缓存中的拥有类型,这是归 BTree 所有。
这种方法会导致未定义的行为吗?
根据我在 The Rustonomicon 中发现的内容,我认为不应该,但由于我在这里使用不安全的代码,我无法完全确定我是否没有违反 BTree 之外的所有权规则,从而导致 UB。
感谢@user2722968,我阅读了更多文档,发现最大的错误是使用从原始指针获取的引用。我决定尝试仅在最低级别使用原始指针,如果使用得当,这“不应该”不会导致 UB。再次感谢@user2722968,如果有人有任何建议请随时评论