vault backup: 2025-11-20 10:44:56
This commit is contained in:
parent
6f31cf3fc2
commit
6a19e45415
57
4.2/2.md
57
4.2/2.md
@ -1,4 +1,4 @@
|
|||||||
- [ ] Умеет писать unsafe код и безопасные обёртки для него.
|
- [x] Умеет писать unsafe код и безопасные обёртки для него. ✅ 2025-11-20
|
||||||
- [x] Понимает причины UB и знает, как его не допустить. (откуда взялось, что значит и как может нанести вред работе программы) ✅ 2025-11-19
|
- [x] Понимает причины UB и знает, как его не допустить. (откуда взялось, что значит и как может нанести вред работе программы) ✅ 2025-11-19
|
||||||
- [x] для автора - проверка ОРа квизами, включая примеры кода. ✅ 2025-11-19
|
- [x] для автора - проверка ОРа квизами, включая примеры кода. ✅ 2025-11-19
|
||||||
- [ ] Умеет проектировать безопасный интерфейс для unsafe кода.
|
- [ ] Умеет проектировать безопасный интерфейс для unsafe кода.
|
||||||
@ -190,7 +190,7 @@ fn check_indicies_valid<const N: usize>(indices: &[usize; N], len: usize) -> boo
|
|||||||
unsafe fn get_entities_at<T, const N: usize>(entities: &mut [T], indices: [usize; N]) -> [&mut T; N] {
|
unsafe fn get_entities_at<T, const N: usize>(entities: &mut [T], indices: [usize; N]) -> [&mut T; N] {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Check that indicies do not overlap and are not out of bound
|
// 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 в цикле
|
// get_unchecked_mut в цикле
|
||||||
let entities: *mut [T] = entities;
|
let entities: *mut [T] = entities;
|
||||||
@ -240,14 +240,61 @@ fn get_entities_at<T, const N: usize>(entities: &mut [T], indices: [usize; N]) -
|
|||||||
```
|
```
|
||||||
Теперь гарантии происходят внутри самой функции.
|
Теперь гарантии происходят внутри самой функции.
|
||||||
Спустя некоторое время вашему коллеге поступило ТЗ, что эта функция должна выдавать индексы только по энтити, которые видимы (пускай будет трейт с методом is_visible). Изменим код:
|
Спустя некоторое время вашему коллеге поступило ТЗ, что эта функция должна выдавать индексы только по энтити, которые видимы (пускай будет трейт с методом is_visible). Изменим код:
|
||||||
**код**
|
```rust
|
||||||
|
fn get_entities_at<T: Visibility, const N: usize>(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:
|
Но останется ли такой код безопасным? Читающему код придётся снова разбирать, какие инварианты нужно соблюсти, чтобы код оставался безопасным. Но этого можно было бы избежать, если бы мы обозначали, какие инварианты мы соблюли. По примеру Safety у функций, у unsafe блоков обозначают соблюдены инварианты словом SAFETY:
|
||||||
```rust
|
```rust
|
||||||
**Kod**
|
fn get_entities_at<T: Visibility, const N: usize>(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 блок в совершенно другом месте. Поэтому, хотелось бы сузить количество кода для поиска бага, поэтому старайтесь уменьшать блоки unsafe.
|
||||||
### Хорош тот unsafe, которого нет
|
### Хорош тот unsafe, которого нет
|
||||||
Лучшее, что можно сделать с unsafe: это не писать его. Если ту же логику можно можно написать в safe rust, то пишите её в safe rust (разве что, можно как небольшое исключение сказать про случай, когда с unsafe мы можем получить хороший прирост производительности в критическом для производительности месте). Стоит что раз подумать, прежде чем писать unsafe код.
|
Лучшее, что можно сделать с unsafe: это не писать его. Если ту же логику можно можно написать в safe rust, то пишите её в safe rust (разве что, можно как небольшое исключение сказать про случай, когда с unsafe мы можем получить хороший прирост производительности в критическом для производительности месте). Стоит сто раз подумать, прежде чем писать unsafe код.
|
||||||
|
## Проектирование безопасного интерфейса
|
||||||
|
Проверка инвариантов делает функцию безопасной, но ее интерфейс может оставаться весьма неудобным. Пользователю придется постоянно проверять, не возникло ли ошибки при выполнении, а еще все равно самому следить, что в функцию подаются валидные данные (так как даже ошибку лишний раз не хочется ловить в продакшене). Поэтому рекомендую сужать интерфейс типами, которые сами по себе будут давать гарантии (к примеру, для нашего кода гарантию на уникальность значений могу бы дать HashSet).
|
||||||
|
## Практика
|
||||||
|
|
||||||
**Начать с проблемы, когда компилятор не может гарантировать безопасность по памяти (но без этого невозможно написать программу), возможно из ub**
|
**Начать с проблемы, когда компилятор не может гарантировать безопасность по памяти (но без этого невозможно написать программу), возможно из ub**
|
||||||
Допустим, на вход вашей функции
|
Допустим, на вход вашей функции
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user