vault backup: 2025-11-23 10:41:26

This commit is contained in:
Akulij 2025-11-23 10:41:26 +07:00
parent 6a19e45415
commit 2ac2b6de11
2 changed files with 75 additions and 3 deletions

View File

@ -5,10 +5,15 @@
- [x] Знает best practice для написания unsafe кода. ✅ 2025-11-19
- [x] Можно использовать примеры из third party крейтов. ✅ 2025-11-19
Умеет писать unsafe код и безопасные обёртки для него.
Понимает причины UB и знает, как его не допустить. (откуда взялось, что значит и как может нанести вред работе программы)
Умеет проектировать безопасный интерфейс для unsafe кода.
Знает best practice для написания unsafe кода.
- [x] Начать с проблемы, когда компилятор не может гарантировать безопасность по памяти (но без этого невозможно написать программу), возможно из ub ✅ 2025-11-19
- [x] Рассказать про причины ub ✅ 2025-11-19
- [x] Рассказать, чем является unsafe, ответственность на программисте, про ub (НЕ является избавлением от borrow checker) ✅ 2025-11-19
- [x] Рассказать про применение unsafe (взаимодействие с С, оптимизация (вспомнить небезопасную либу для бэкенда: rocket или actix), написание основы/базы языка) ✅ 2025-11-19
- [x] Рассказать про применение unsafe (взаимодействие с С, оптимизация (вспомнить небезопасную либу для бэкенда: rocket или actix), написание основыубазы языка) ✅ 2025-11-19
- [x] Определении функции unsafe если соблюдение инвариантов висит на пользователе (при написании такой функции смотреть - является ли сам интерфейс функции safe) ✅ 2025-11-19
- [x] примеры из third party ✅ 2025-11-19
- [ ] Рассказать про бест практис при написании unsafe:
@ -84,11 +89,25 @@ fn main() {
- Дереференс сырого указателя
- Вызов unsafe функции (к примеру, определенной через extern)
- Чтение/запись mut static переменной
- Имплиментация unsafe трейта
- Имплементация unsafe трейта
- Доступ к полям union
- Объявление extern блока (использовали его, когда объявляли C функции)
- Использование unsafe атрибута (к примеру, `#[unsafe(no_mangle)]`, использованный ранее)
- Вызов функции с таким `target_feature`, который в текущей функции не определен (к примеру, вызов функции, для которой требуется расширение x86_64 `avx` из функции, для вызова которой не требуются никакие расширения)
Квиз - Множественный выбор
Какой функционал из этого НЕ добавляет ключевое слово unsafe, по сравнению с safe кодом?
Дереференс сырого указателя
Нет, дереференс сырого указателя можно выполнить только в unsafe блоке
Чтение mut static переменной
Нет, читать изменяемую статическую переменную, как и записывать в нее, можно только из unsafe блока
Дереференс ссылки
Правильно, дереференс ссылки можно выполнить и в safe и в unsafe коде
Запись в const static переменной
Нет, записывать в константную статическую переменную нельзя даже в unsafe блоке
Использование атрибута target_feature над функцией
Правильно, для использования такого аттрибута можно и без unsafe, так как сам по себе он не добавляет никакого неопределенного поведения
## Undefined Behavior
ОР - Понимает причины UB и знает, как его не допустить. (откуда взялось, что значит и как может нанести вред работе программы)
@ -147,6 +166,8 @@ fn get_entities_at<T, const N: usize>(entities: &mut [T], indices: [usize; N]) -
- индексы могут больше, чем длина массива, что приведет к невалидным референсам, либо чтению/записи из не аллоцированной памяти, что привидет к панике
Такой код - проблема для безопасности всего кода, но мы можем её решить: пометив ее, как unsafe, либо сделав её логику безопасной
### Путь первый - unsafe функция
ОР - Знает best practice для написания unsafe кода.
ОР - Умеет писать unsafe код и безопасные обёртки для него.
Если функция может принимать данные, которые могут быть невалидными и вызывать undefined behavior, такая функция должна быть помечена ключевым словом unsafe перед fn, чтобы сказать пользователю функции, что при ее использовании нужно дополнительное внимание, а компилятору - что здесь есть не безопасные операции, и поэтому быть вызвана такая функция может только в блоке unsafe. Теперь интерфейс выглядит так:
```rust
/// Returns mutable references to many indices at once
@ -224,6 +245,8 @@ pub const unsafe fn new_unchecked(ptr: *mut T) -> Self {
}
```
### Путь второй - оборачивание в безопасную обертку
ОР - Знает best practice для написания unsafe кода.
ОР - Умеет писать unsafe код и безопасные обёртки для него.
unsafe как эффект, не должен распространяться бескончено вверх по графу вызова, иначе всё было бы unsafe. Поэтому в какой-то момент эти инварианты должны гарантироваться. Поэтому, давайте обернем наш код в безопасный, гарантировав инварианты:
```rust
fn get_entities_at<T, const N: usize>(entities: &mut [T], indices: [usize; N]) -> Result<[&mut T; N], ()> {
@ -291,10 +314,55 @@ fn get_entities_at<T: Visibility, const N: usize>(entities: &mut [T], indices: [
Теперь при изменении можно просто перепроверить соблюдение инвариантов.
Но, оно нужно не только для этого. При написании такого комментария программист лишний раз подумает, какие инварианты нужно соблюсти. А ещё, упрощает нахождение бага, так как можно сравнить условия, когда возникает баг, с теми условиями, что прописаны в комментариями. И баг по безопасности будет возникать возникать только в unsafe блоках. Правда, при всплытии в одном месте, причиной возникновения может стать unsafe блок в совершенно другом месте. Поэтому, хотелось бы сузить количество кода для поиска бага, поэтому старайтесь уменьшать блоки unsafe.
### Хорош тот unsafe, которого нет
ОР - Знает best practice для написания unsafe кода.
Лучшее, что можно сделать с unsafe: это не писать его. Если ту же логику можно можно написать в safe rust, то пишите её в safe rust (разве что, можно как небольшое исключение сказать про случай, когда с unsafe мы можем получить хороший прирост производительности в критическом для производительности месте). Стоит сто раз подумать, прежде чем писать unsafe код.
## Проектирование безопасного интерфейса
ОР - Умеет проектировать безопасный интерфейс для unsafe кода.
ОР - Умеет писать unsafe код и безопасные обёртки для него.
Проверка инвариантов делает функцию безопасной, но ее интерфейс может оставаться весьма неудобным. Пользователю придется постоянно проверять, не возникло ли ошибки при выполнении, а еще все равно самому следить, что в функцию подаются валидные данные (так как даже ошибку лишний раз не хочется ловить в продакшене). Поэтому рекомендую сужать интерфейс типами, которые сами по себе будут давать гарантии (к примеру, для нашего кода гарантию на уникальность значений могу бы дать HashSet).
## Практика
ОР - Умеет писать unsafe код и безопасные обёртки для него.
Для функции `strerror_r` из стандартной библиотеки C напишите extern (Для определения функции используйте `man strerror` на unix системах или [онлайн](https://www.man7.org/linux/man-pages/man3/strerror.3.html)), и безопасную обертку для него, используя безопасные типы. Пусть безопасная обертка будет принимать внешний буфер для записи, но для упрощения не будет делать релокацию при ошибке.
Подсказки:
Референсы являются безопасной версией сырых указателей
В случае ошибки возвращется std::io::Error
Чек-лист:
Интерфейс функции использует безопасные типы, валидные при любом значении и не позволяет вызывать undefined behavior
Для unsafe блоков прописаны SAFETY комментарии
Не возникает undefined behavior
Решение:
```rust
unsafe extern "C" {
fn strerror_r(errnum: c_int, strerrbuf: *mut c_char, buflen: usize) -> c_int;
}
fn os_error_stringify(code: i32, str: &mut String) -> Result<(), std::io::Error> {
let buf = str.as_mut_ptr();
let len = str.capacity();
// SAFETY: pointer is valid and len is within allocated buffer
let err = unsafe { strerror_r(code as c_int, buf as *mut _, len) };
match err {
0 => {
// SAFETY: len is within capacity and
// uninitialized bytes will be shrinked later
unsafe { str.as_mut_vec().set_len(len) };
let null_position = str.as_bytes().iter().position(|byte| *byte == 0).unwrap_or(0);
// SAFETY: null_position is within capacity and
// all items are before null are valid utf-8 bytes
unsafe { str.as_mut_vec().set_len(null_position) };
Ok(())
},
err => {
// Now string is probably invalid, so set len 0
// SAFETY: length 0 is always valid
// using vec set_len so we are not freeing buffer for reusability
unsafe { str.as_mut_vec().set_len(0) };
Err(std::io::Error::from_raw_os_error(err))
}
}
}
```
*Тут возможна вариация и с CString, но так как strerror_r записывает только ascii символы (совместимые с utf-8), то обычный String упрощает взаимодействие с интерфейсом*
**Начать с проблемы, когда компилятор не может гарантировать безопасность по памяти (но без этого невозможно написать программу), возможно из ub**
Допустим, на вход вашей функции

View File

@ -1 +1,5 @@
Plugins lesson 3.
| | |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Умеет формировать API для подключения плагинов. <br> <br>Умеет создавать и собирать плагины в соответствии с заданным API. <br>(C) <br> <br>Умеет подключать и отключать плагины в рантайме. (загрузка библиотеки и ее выгрузка) | На примере торговых ботов для биржи создадим систему с возможностью подключения плагинов. Научимся формировать API для плагинов, собирать и подключать их в рантайме, а также управлять ими — включать и отключать по мере необходимости. <br> <br>Урок - практический. Студент после него сможет писать аналогичные решения, что мы и проверим в проекте. <br> <br>Торговый бот - контекст, в котором показываются примеры (не production bot) <br> <br>Придумать API |