From 6a19e454152c34fb94281a9c0d53bbd89ec49a42 Mon Sep 17 00:00:00 2001 From: Akulij Date: Thu, 20 Nov 2025 10:44:56 +0700 Subject: [PATCH] vault backup: 2025-11-20 10:44:56 --- 4.2/2.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/4.2/2.md b/4.2/2.md index fbf920f..826dee9 100644 --- a/4.2/2.md +++ b/4.2/2.md @@ -1,4 +1,4 @@ -- [ ] Умеет писать unsafe код и безопасные обёртки для него. +- [x] Умеет писать unsafe код и безопасные обёртки для него. ✅ 2025-11-20 - [x] Понимает причины UB и знает, как его не допустить. (откуда взялось, что значит и как может нанести вред работе программы) ✅ 2025-11-19 - [x] для автора - проверка ОРа квизами, включая примеры кода. ✅ 2025-11-19 - [ ] Умеет проектировать безопасный интерфейс для unsafe кода. @@ -190,7 +190,7 @@ fn check_indicies_valid(indices: &[usize; N], len: usize) -> boo unsafe fn get_entities_at(entities: &mut [T], indices: [usize; N]) -> [&mut T; N] { unsafe { // Check that indicies do not overlap and are not out of bound - debug_assert!(check_indicies_valid(&indices, entities.len())) + debug_assert!(check_indicies_valid(&indices, entities.len())); // преобразование референса в указатель, чтобы можно было вызвать // get_unchecked_mut в цикле let entities: *mut [T] = entities; @@ -240,14 +240,61 @@ fn get_entities_at(entities: &mut [T], indices: [usize; N]) - ``` Теперь гарантии происходят внутри самой функции. Спустя некоторое время вашему коллеге поступило ТЗ, что эта функция должна выдавать индексы только по энтити, которые видимы (пускай будет трейт с методом is_visible). Изменим код: -**код** +```rust +fn get_entities_at(entities: &mut [T], indices: [usize; N]) -> Result<[&mut T; N], ()> { + if !check_indicies_valid(&indices, entities.len()) { + return Err(()); + } + + let mut entities: Vec<_> = entities.into_iter().filter(|entity| entity.is_visible()).collect(); + let entities: &mut [&mut T] = entities.as_mut(); + + let output = unsafe { + debug_assert!(check_indicies_valid(&indices, entities.len())); + // преобразование референса в указатель, чтобы можно было вызвать + // get_unchecked_mut в цикле + let entities: *mut [&mut T] = entities; + // просто аллокация памяти на стеке с неинициализироваными значениями + // Небольшая оптимизация, так как лишний раз не записываем нули, + // которые потом все равно будут перезаписаны + let mut output: [&mut T; N] = MaybeUninit::uninit().assume_init(); + for i in 0..N { + let index: usize = *indices.get_unchecked(i); + output[i] = (&mut *entities).get_unchecked_mut(index); + } + + output + }; + + Ok(output) +} +``` Но останется ли такой код безопасным? Читающему код придётся снова разбирать, какие инварианты нужно соблюсти, чтобы код оставался безопасным. Но этого можно было бы избежать, если бы мы обозначали, какие инварианты мы соблюли. По примеру Safety у функций, у unsafe блоков обозначают соблюдены инварианты словом SAFETY: ```rust -**Kod** +fn get_entities_at(entities: &mut [T], indices: [usize; N]) -> Result<[&mut T; N], ()> { + if !check_indicies_valid(&indices, entities.len()) { + return Err(()); + } + + let mut entities: Vec<_> = entities.into_iter().filter(|entity| entity.is_visible()).collect(); + let entities: &mut [&mut T] = entities.as_mut(); + + // SAFETY: the code is safe because we just checked that + // indicies do not overlap and are not out of bound + let output = unsafe { + // тот же код, что и был + }; + + Ok(output) +} ``` +Теперь при изменении можно просто перепроверить соблюдение инвариантов. Но, оно нужно не только для этого. При написании такого комментария программист лишний раз подумает, какие инварианты нужно соблюсти. А ещё, упрощает нахождение бага, так как можно сравнить условия, когда возникает баг, с теми условиями, что прописаны в комментариями. И баг по безопасности будет возникать возникать только в unsafe блоках. Правда, при всплытии в одном месте, причиной возникновения может стать unsafe блок в совершенно другом месте. Поэтому, хотелось бы сузить количество кода для поиска бага, поэтому старайтесь уменьшать блоки unsafe. ### Хорош тот unsafe, которого нет -Лучшее, что можно сделать с unsafe: это не писать его. Если ту же логику можно можно написать в safe rust, то пишите её в safe rust (разве что, можно как небольшое исключение сказать про случай, когда с unsafe мы можем получить хороший прирост производительности в критическом для производительности месте). Стоит что раз подумать, прежде чем писать unsafe код. +Лучшее, что можно сделать с unsafe: это не писать его. Если ту же логику можно можно написать в safe rust, то пишите её в safe rust (разве что, можно как небольшое исключение сказать про случай, когда с unsafe мы можем получить хороший прирост производительности в критическом для производительности месте). Стоит сто раз подумать, прежде чем писать unsafe код. +## Проектирование безопасного интерфейса +Проверка инвариантов делает функцию безопасной, но ее интерфейс может оставаться весьма неудобным. Пользователю придется постоянно проверять, не возникло ли ошибки при выполнении, а еще все равно самому следить, что в функцию подаются валидные данные (так как даже ошибку лишний раз не хочется ловить в продакшене). Поэтому рекомендую сужать интерфейс типами, которые сами по себе будут давать гарантии (к примеру, для нашего кода гарантию на уникальность значений могу бы дать HashSet). +## Практика **Начать с проблемы, когда компилятор не может гарантировать безопасность по памяти (но без этого невозможно написать программу), возможно из ub** Допустим, на вход вашей функции