Грабли все таки делись.
Как пример могу привести адаптированный код хеш таблицы.pub struct RawTable<T, A: Allocator + Clone = Global> {
table: RawTableInner<A>,
// Tell dropck that we own instances of T.
marker: PhantomData<T>,
}
pub struct RawTableInner<A> {
bucket_mask: usize,
ctrl: *const u8,
growth_left: usize,
items: usize,
alloc: A,
}
pub fn get<F>(&self, hash: u64, eq: F1) -> Option<&T>
where
F: FnMut(&T) -> bool,
{
match self.find(hash, eq) {
// bucket = *mut T
Some(bucket) => Some(unsafe { &*bucket }),
None => None,
}
}
Прикол в том что вроде бы как RawTableInner хранит сырой указатель на аллоцированную память и полностью небезопасен. Но далее привязываем данную таблицы к RawTable + PhantomData что говорит компилятору что у нас владеющая структура. Потом определяем метод get, который внутри использует метод find. Find возвращает сырой указатель. Но!!! Мы ведь определили метод get у &RawTable, а также привязали выходную ссылку на значение к времени жизни самой RawTable, то есть выходная ссылка будет всегда валидна, так как нельзя вернуть ссылку на удаленную таблицу.
То есть, метод find который возвращает полностью небезопасный "сырой указатель" который может существовать даже после удаления RawTable, и по определению unsafe, превратился полностью в safe. Так как с помощью метода get мы явно привязали жизнь сырого указателя к жизни RawTable. Кроме того мы привязали его не просто к таблице. Мы еще и заблокировали изменения самой RawTable, пока существует на него ссылка. И все это в 7 строк кода.
Собственно вот так и происходит переход из unsafe в safe.