diff --git a/4.2/2.md b/4.2/2.md index 826dee9..99f5bd9 100644 --- a/4.2/2.md +++ b/4.2/2.md @@ -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(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(entities: &mut [T], indices: [usize; N]) -> Result<[&mut T; N], ()> { @@ -291,10 +314,55 @@ fn get_entities_at(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** Допустим, на вход вашей функции diff --git a/4.2/3.md b/4.2/3.md index 86c9a6c..5c47041 100644 --- a/4.2/3.md +++ b/4.2/3.md @@ -1 +1,5 @@ -Plugins lesson 3. \ No newline at end of file +Plugins lesson 3. + +| | | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Умеет формировать API для подключения плагинов.

Умеет создавать и собирать плагины в соответствии с заданным API.
(C)

Умеет подключать и отключать плагины в рантайме. (загрузка библиотеки и ее выгрузка) | На примере торговых ботов для биржи создадим систему с возможностью подключения плагинов. Научимся формировать API для плагинов, собирать и подключать их в рантайме, а также управлять ими — включать и отключать по мере необходимости.

Урок - практический. Студент после него сможет писать аналогичные решения, что мы и проверим в проекте.

Торговый бот - контекст, в котором показываются примеры (не production bot)

Придумать API | \ No newline at end of file