Search
Write a publication
Pull to refresh
3
0
Кривенков Роман @qwerty19106

Embedded

Send message

Проблема, о которой я знаю, связана с кодом, который девелопит один конкретный человек.

А там где куча людей, очевидно нужно разделять ответственность, а значит и документировать интерфейсы.

Они может и согласны, но их туда никто не пустит, т.к. обычно это зона ответственности других людей, и делается уже отдельным патчем от них.

Соответственно на всё огромное ядро проблема вылезла ровно в том месте, где один человек написал и внутренние интерфейсы, и сами драйвера, и ничего из этого не задокументировал. И он же пишет что не будет документировать чтобы не пустить в этот код других разработчиков на С в том числе.

Так может дело не в языке программирования, а в синдроме вахтера?

А как это поможет в компиляции драйвера на С при изменении сишных интерфейсов?

Основные проблемы на линуксе возникли из-за отсутствия документации интерфейсов на С, и отказа некоторых разработчиков этих интерфейсов их документировать. Это на хабре уже раз 5 разжевывали.

А с бинарной совместимостью нет никаких проблем, на внешние интерфейсы включаем extern "C", точно так же как и в любых других языках.

Вопрос был про Rust: он ближе к чистому C или к C++?

Стандартные строки std::String ближе к С++, std::OsString и heapless::String ближе к С.

И если тебе нельзя делать realloc

Можно обернуть в Pin, и компилятор запретит делать realloc, и даже move-семантику.

... либо по-другому: используем те строки, которые выданы. Например, в модулях bios (uefi application) где-то используется CHAR8, где-то CHAR16. И код пишется где-то под одни символы, где-то под другие. Да, неуниверсально. Но меньше накладных расходов и ошибок. Опять же вопрос: а что Rust?: Разные строки, разные типы, работаем без конвертаций?

Стандартный и удобный тип один - это std::String. Но библиотеками с const методами (аналог const-expr из С++) можно конвертировать в любую кодировку. В результате нет накладных расходов, и довольно удобно.

Я этим пользуюсь вообще на микроконтроллерах, где нужно передавать в датчики ASCII строки (и иногда старые кодировки, типа latin-1). Получается примерно такой код:
let str_bytes: &'static [u8] = (const { "abcdefg".into_ascii() }).to_bytes();

В этой строке нет копирования байт, она превращается просто в указатель на flash память.

такое решение может быть немного узким: всё-таки стек валиден только в самой функции и вложенных, вышел из функции и "карета превратилась в тыкву".

Любую строку (и любой другой тип), которая лежит на стеке, можно явно переместить в кучу через alloc::Box (std::Box). Это зависит от задачи, но во многих случаях достаточно стека, т.к. есть еще return impl Trait.

Кроме того в случае ошибки код не упадет с сегфолтом как в С, а просто не скомпилируется.

в котором можно обойтись без неявного выделения памяти (не только строки, но и массивы и т.д.).

В Rust есть ключевой крейт heapless, которым пользуются во всех no_stdпрограммах (драйвера, ядро, embedded). И в данном случае alloc и куча вообще были не нужны. Их автор притянул за уши скорее чтобы показать "смотри как я могу без стандартной библиотеки".

Возможно ли в Rust использовать те типы данных, которые даны, а не натягивать "сову на глобус"?

В С и других языках те же проблемы: либо конвертируем utf-8 в utf-16, либо делаем функцию (семейство функций) вывода в лог, которая умеет все кодировки. Если в языке этого сходу не видно, то оно делается неявно (что еще хуже) внутри стандартной библиотеки.

или, возможно, в Rust строки не отъедают динамическую память?

std::String в куче, но если меньше N байт, то на стеке. heapless:String всегда на стеке, и есть много других реализаций.

Согласен.
Но у программиста все-равно должен быть способ прыгнуть сразу в "быструю" точку входа, т.к. компилятор не всегда может выкинуть проверки.

Например, сейчас я пишу реализацию кольцевого буфера. И в методе write проверяю что размер входных данных (count) меньше size_t / 2. Размер буфера тоже меньше size_t / 2.

Это значит что любые проверки на переполнение для head + count и tail + count можно выкинуть, но компилятор об этом никак не догадается.

По хорошему должно быть 2 библиотечных метода:

1) принимает любые значения, проверяет их на ошибки.
Название короткое и удобное.

2) принимает любые значения, не проверяет их на ошибки (UB)
Длинное неудобное название (натипа xyz_unchecked), и требуется явное указание компилятору, что ты всё делаешь правильно.

они там явно рекомендуют не использовать некоторые опасные и сомнительные конструкции

Ключевое слово тут "некоторые", причем очень небольшой список. То, что я выше описал, Core Guildelines никак не исправляет.

Как и проблемы, пришедшие из С. Например, вы знаете, что такое Integer Promotion?

но вероятность отстрелить себе ногу больше из-за гораздо большего количества ножной ручной работы.

Но с другой стороны, UB в коде на С гораздо легче заметить глазами. Да и статическим анализаторам намного проще: гораздо меньше ложно-положительных и ложно-отрицательных случаев.

А вы сможете утверждать, что знаете все несколько сотен UB в С++? Вот в С их вполне реально выучить все.

Это распространенное заблуждение. С++ унаследовал почти все UB из С, и добавил гораздо больше новых.
Например при работе со string_view легко получить UB, как и в С с нуль-терминированными строками.
Ну а move-семантика отстреливает ноги на каждом шагу. Такое чувство что в C получить use-after-free даже сложнее.

Начинаете оду расту за отсутствие GC, но это благодаря RAII и деструкторам Drop trait'у, scope'ам и lifetime'ам, и система типов с компилером тут ни при чём, это старо как мир, и не присуще только rust'у. А вот борьба с пресловутым borrow checker'ом как раз выливается в использование Rc/Arc (reference counting), что некоторыми считается аналогом GC (потому что это не-мануальный мемори менеджмент, пусть и не полноценный GC). Афинные и алгебраические типы, паттерн матчинг - это всё для красного словца.

Про качество статьи согласен, нафиг такую рекламу Rust. Но вы и сами набрасываете, и довольно нелепо:
- так в каком языке есть лайфтаймы, старые как мир?
- reference counting никак не может считаться аналогом GC, т.к. выполняется сразу за счет деструкторов и RAII.
- магии не существует, и все языки без GC активно используют reference counting (особенно С++), так что в этом плохого?

За наброс на плюсы обидно, наброшу в ответ, напомнив про банальный кейс "rust panics on overflow"

Опция overflow-checks для Cargo.toml существует с выхода Rust 1.0, 10 лет назад.

В плюсах не так много UB

В плюсах столько UB, что их полный список невозможно запомнить. Их буквально несколько сотен. Есть даже целые книги, посвященные UB (https://github.com/Nekrolm/ubbook/, https://pvs-studio.ru/ru/blog/posts/cpp/1129/).

Многие из них крайне не очевидные и не ловятся статическими анализаторами. Санитайзер не поможет в коде, который управляет критически важным оборудованием (АЭС, спутники, самолеты и т.д), т.к. всё уже взорвется/упадёт.

"память течёт? - это не проблема

Это проблема, когда она течет. Но какое отношение это имеет к языку?
Утечку памяти легко организовать через 2 reference counting в любом языке. Предъявляйте претензии к конкретным библиотекам.

И, всё таки, утечка памяти не позволит кому то совершить взлом на лярд долларов, в отличие от UB.

Вероятно вы просто не сталкивались с этими проблемами. Например, если SPI Master раньше времени отпустит CS, то SPI Slave (проверено на STM32F103 и STM32F411) зависнет в неопределенном состоянии, т.к. в сдвиговом регистре будет от 1 до 7 бит. И при следующем включении SPI он начнет с этого места, а не с 0 бита. Лечится это обычно сбросом тактования SPI, о чем HAL естественно не знает, и молча делает дичь.

И ваши трудности с настройкой железа к выбору языка программирования тоже отношения не имеют.

Но ведь это вы изначально заявили про "сырой язык". Согласен, давайте его обсуждать. Какие будут ваши доказательства?

Я наоборот вижу в нем смысл именно для МК, RTOS или драйверов ОС.

1) Библиотеки. Ничего уровня embassy или defmt в С нет и не предвидится. В С++ кстати тоже.
2) Безопасность. Нет множества UB, которые в С приходится проверять санитайзерами, хотя это просто не должно было скомпилироваться.
3) Быстродействие. Ассемблер на выходе такой же, а местами и чище чем в С, т.к. у компилятора больше возможностей для оптимизации.
Ну и памяти есть меньше, см. например null pointer optimization, который вообще не возможен в С.

Распространенность вообще ортогональна сырости. Есть куча примеров популярных сырых технологий и непопулярных отточенных. Другие аргументы про "сырой язык" будут?

Что касается HAL. Вы же не будете спорить, что STM32 HAL на каждый чих делает кучу действий, которые в нормальном коде занимают 1-2 такта?

А как насчет поддержки всех режимов работы периферии? Как в нем выключить только Noise Error, не выключая Framing Error в UART? Как написать раельно работающий SPI Slave, или еще хуже I2C Slave? Каждый раз приходится после функций HAL менять вручную биты в регистрах.

Сделать кастомное USB (не CDC или HID) устройство вообще не возможно. Проще полностью выкинуть код USB из HAL, и написать свою библиотеку USB.

Можете ли вы аргументировать про "сырой язык"?
Например на микроконтроллерах STM32 библиотеки на Rust значительно менее сырые, чем официальный HAL на С.

В продакт коде однократные вычисления при старте программы можно не считать. Так что я бы сделал Lazy static переменную. А этот подход конечно треш.

Вот в этом и беда, что в С++ есть куча разных HAL, и нет ничего универсального. Взгляните для сравнения на embedded-hal для Rust. Если у меня проект на STM32, и вдруг понадобился WiFi, то я меняю 10 строчек кода, и 2 строки в системе сборки (Cargo.toml), и компилирую под ESP32. В С++ мне нужно переписать весь код работы с периферией.

Что мешало это сделать на С++ за ***цать лет? Да вроде ничего, язык позволяет.
Но этого нет, и вероятно уже не будет. Потому что поезд уже ушел...

Ок, тот же самый пример: конечный цикл бесконечным c -O1 и -O2.

UB это UB при любом уровне оптимизация, иногда даже при -O0.

Тогда зачем же вы используете это UB? Может надежнее сделать ассемблерную вставку?

Information

Rating
6,602-nd
Location
Ижевск, Удмуртия, Россия
Date of birth
Registered
Activity