Тут надо знать контекст, над чем он работал. Из его объяснения не понятно, откуда такие проблемы с временами жизни. Я видел кучу проблем при интеграции с другими языками, и там иногда проще обернуть в Rc или Arc. Но при работе над библиотекой на чистом Rust таких проблем не возникает.
Времена жизни обычно указываются явно в 2 случаях: - взятие ссылки на поле/поля/слайсы - оборачивание ссылки на исходный объект В остальных случаях их опускают, и они вычисляются компилятором. При таком подходе не будет возникать "upwards of 30 or more statements depending on the complexity of your architecture".
С другой стороны, "This is because the borrow checker cannot run until an entire function compiles. Sometimes it seems to refuse to run until my entire file compiles." действительно встречается. Это мешает если не знаешь правила заимствования, и пишешь надеясь что компилятор тебя поправит. Не сказал бы что это проблема, в любом языке желательно "для того, чтобы быть высокопродуктивным программистом Rust ... нужно чтобы вы сделали все правильно с первого раза".
Однако и он пишет, что текущая ситуация в Rust гораздо лучше чем в С++:
This is painful because I am an experienced C++ programmer, and C++ has this exact problem except worse: undefined behavior. In the worst case, C++ simply doesn’t check anything, compiles your code wrong, and then does inexplicable and impossible things at runtime for no discernable reason (or it just deletes your entire function). If you run ubsan (undefined behavior sanitizer), it will at least explode at runtime with an error message.
в отличие от фразы выше "потому что Rust отстает по фичам от C++ лет на 15", которую плюсуют но не аргументируют.
То что вы вспоминаете, было еще до версии 1.0 языка. По определению, разработчики могли менять API до выхода 1.0.
В языке есть правила переполнения целочисленных типов, которые я привел выше. Вы же хотите чтобы стандартная библиотека им не следовала. Если вы запустите в Release режиме, то будет нужное вам поведение (пруф). Так полезен метод или бесполезен?)
окажется что в "safe rust" unsound unsound-ом погоняет
Пример в студию!
что в "unsafe rust" больше шансов поймать UB чем в Си
Сильное утверждение, доказать сможете?
что это чудо при разработке сначала постоянно вынуждает делать глубокие инвазивные рефакторинги по все кодовой базе (потому что отстает по фичам от C++ лет на 15)
Не встречал проблем с рефакторингом. Приведите пример. Из фич знаю только вычисление в const expr, тут пока в Rust не очень. И это явно не 15 лет существует в С++. По остальным я тоже могу привести фичи, в которых С++ отстает на N лет, и которых в нем никогда не будет.
Оказалось что в расте не обрабатываются ошибки аллокации при создании структур (Vec::new()), а так как машинерии для работы с исключениями у них тоже нет, то им пришлось наспамить кучу новых методов типа Vec::try_new() для всех структур в стандартной библиотеке.
Наспамили, работает, пишут новый код в ядре Linux. Это в вас говорит обида, что С++ в ядро не попал.
либо, как вариант, молча лепит минусы в карму
Это вообще традиция на Хабре, не зависимо от обсуждаемой темы. Хорошо что хоть некоторые так не делают.
Автор не знаком с концепцией ZeroVer. Она распространена не только в Rust, в Javascript например она никого не смущает.
std::ops::Tryдоступен только в Nightly компиляторе, это "экспериментальная фича компилятора", которой никогда не было в Stable. Видимо автор всегда использует только нестабильные версии компиляторов в продакшен, ну ОК.
attempt to multiply with overflow Ну да, в Debug режиме Rust обрабатывает переполнение как ошибку. И можно выключить черезoverflow-checks = false, или включить аналогично в Release. Если нужно особое поведение, напиши его сам, и подай RFC на включение в стандартную библиотеку.
И т.д. Очень жирный наброс, ни одной нормальной претензии.
Ну и что будет делать старый код с новой версией? Этот подход вообще перпендикулярен "трюк с добавлением новых полей в структуру", т.к. на каждую версию пишется/генерится отдельный код парсинга.
Rust — хорош! Мне нравится :) Однако преимуществом C++ является широкий круг задач, который он решает: игры, бэкенд, работа с железом, десктопные приложения и другое. Мало какой язык может похвастаться таким широким обхватом. (Только C может.) Но не бывает достоинств без недостатков, широкий круг задач делает сложным создание универсального инструмента управления проектами.
Широкий круг задач тянет за собой кроссплатформенность, удобные системы сборки и прочий тулинг. С этим на С++ настолько плохо, что на Linux, Windows и Android он отдает свои позиции любым языкам с виртуальной машиной и сборкой мусора.
Остается достаточно узкий круг задач, в которых нужно максимальное быстродействие или близость к железу: игры, HighLoad, embedded.
Rust пытается заходить во все эти ниши, но, AFAIK, сколько-нибудь заметное распространение получил пока только в бэкенд разработке. Вот Ваши задачи можно решать с помощью Rust'а? Не в теории, а на практике? Есть ли необходимые крейты? Наладить инфраструктуру точно будет проще?
Любой язык пытается заходить во все ниши. Например Javascript вышел из браузеров и потеснил Python в разработке оконных приложений. Не вижу в этом ничего плохого.
Очень многие начали. Cloudflare целиком на Rust переписались, в исходниках Android его уже столько же, сколько C, AWS несколько сервисов целиком переписали и не останавливаются (и сделали Firecracker целиком на нем, опенсорс менеджер микровиртуалок, на котором работают Lambda и Fargate), с голенга многие тоже переписываются. Я могу дать полный список известных мне проектов, там от MESA до Figma, Vercel и части бэка npm.js репозитория. И под спутники пишут на нем, и новые проекты от мала до велика (от Tauri, который по сути Electron здорового человека, и Embassy, который embedded просто и быстро, до Western Digital, которые платформу обработки данных на 5 петабайт в день на нем пилят).
В микроконтроллерах, например, количество новых проектов на С++ стремится к 0, Rust уже соотносится с С примерно 30/70%. Сейчас ситуация такова, что найти библиотеку для датчика на Rust проще чем на С (это к вопросу о количестве крейтов). В других же нишах, например десктопные приложения, Rust вероятно никогда не будет распространен.
HAL это как раз абстрактный уровень, не привязанный к МК.
Вот именно. Но в С и С++ почему-то в каждом HAL свой API.
Вы понимаете как периферия в МК устроена? В разных МК периферия устроена по своему, она висит на разных адресах и биты в адресах везде устроены по своему.
Понимаю. По-этому под каждое семейство МК написана своя реализация embedded-hal, о чем я писал выше.
А по поводу, есть ли на C/C++ некий общий драйвер: как пример Zephyr RTOS, оно вроде как более менее живое.
Прямо на сайте у них написано "750+ Boards Supported". Остальное не поддерживается, например любая кастомная плата. И в 99% случаев нужно делать именно кастомную плату.
Вообще потуг было очень много, но они просто особо никому не нужны оказались, такие вещи неплохи для домашних поделок, но в ответственных решениях или сами пишут или уже есть своя внутренняя экосистема
А как же переиспользование кода? Разве не лучше чтобы было 1-2 драйвера на гитхабе, которые пилят всем миром. Чем 1-2 на каждое семейство МК (итого штук 20), каждый со своими багами.
Вы либо за "повторное использование кода", либо против, уж определитесь.
P.S. То что в мире С и С++ на МК не прижилось "повторное использование кода" не говорит о том, что сам принцип плох.
Это то же самое, что сказать - язык arduino лучше c/c++, язык простой, драйвер один на огромное число мк
На с++ можно было бы сделать так: 1) абстрактный класс с методами работы с SPI например - аналог embedded-hal 2) реализацию этого класса для esp32 - аналог esp32-rs 3) драйвер для датчика MPU9250, в который передается ссылка на абстрактный класс из п.1 В итоге будет точно также один драйвер на любой МК и архитектуру.
Это то же самое, что сказать - язык arduino лучше c/c++, язык простой, драйвер один на огромное число мк
За цать лет в С++ этого не сделали и, вероятно, уже никогда не сделают. Я не знаю, что помешало. Вероятно просто не смогли договориться про общий универсальный API. А в Rust уже есть, работает, и пишется много нового кода (я не призываю переписывать старый код).
А api очень агрессивно менялся в разных версиях, что приводит к поломкам при обновлении крейтов.
Как он может меняться, если эти библиотеки реализуют трейты из embedded-hal? Может вы вспоминаете давние времена, когда embedded-hal был версии 0.3?
А что касается linux, я ради интереса написал на мерзком с++ код, использующий прерывания /dev/rtc и аналогичный на безопасном rust. На rust код так и не осилил настройку частоты прерывания и его корректное использование. Но зато там была пачка unsafe. На с++ код завелся сразу. Это пример из личной практики.
Вы не правильно понимаете безопасность в Rust. Работа с прерываниями на любом языке будет unsafe по своей природе. Над этим кодом в любом языке надо писать safe обертку, и использовать её из прикладного кода. Rust просто дает инструменты, которые разделяют эти 2 этапа.
Мой пример из личной практики: драйвер тепловизора для async/await, работает по прерываниям через интерфейсы I2C и SPI. Запускал на Raspberry Pi4, кросскомпиляция на amd64 делается ровно 2 строчками (сравните с С и С++). На весь проект 3 unsafe. Можно и без них, но будет лишнее копирование картинки. Впрочем возможно в std уже что-то завезли, и сейчас можно без unsafe.
Не знаю как там в OS. Но я, например, пишу под микроконтроллеры (stm32, esp32, nrf), которые на несколько ступеней ниже операционной системы. Весь код построен на прерываниях, DMA и RTOS.
Так вот, на Rust код превосходит С++ по всем параметрам: читаемость, безопасность, библиотеки, инструменты. Достаточно упомянуть простой факт: драйвер для конкретного датчика на Rust ровно один универсальный, подходит для любого семейства микроконтроллеров (см. https://github.com/rust-embedded/embedded-hal). На С++ же полный зоопарк.
Справедливости ради, год назад в библиотеках было похуже. И текущая отличная ситуация - это из-за отсутствия старого кода и обратной совместимости с ним.
Вот только думать о владении и времени жизни гораздо проще для небольших кусков кода, а не для всех 100%. Ну если конечно вы не робот, который всё делает идеально. Я вот не робот.
Практика показывает, что компиляторы проверяют код лучше людей, и что ошибаются все люди, независимо от опыта.
Кроме того лучше написать чуть более медленный код, но в котором компилятор автоматически вставил проверки. А 95% тормозов убрать оптимизацией тех самых 5% кода.
Да, я embedded и имел ввиду. Могу подтвердить, что память корутин лежит в preallocated storage, а куча вообще выключена.
С помощью некоторых ухищрений можно заставить компилятор вычислить максимальный размер памяти корутины, а не выдавать каждой по N байт. При этом стек общий и невозможно переполнением стека испортить чью-то память (на самом деле можно, но это уже вопрос того где линкер помещает статические переменные, и можно настроить так чтобы было нельзя).
В реальном коде все делают обертку на С (кодогенерацией с небольшими подпорками), над ней обертку на unsafe Rust (полностью автоматической кодогенерацией), и над ней обертку на safe Rust (вручную, но иногда часть удается автоматизировать). Вот хороший пример со всеми 3 уровнями оберток: https://github.com/twistedfall/opencv-rust.
А эти истории про переписывание из С++ на Rust скорее делают антирекламу, но по факту составляют примерно 0% кода.
Согласен, текущая реализация итераторов такое не позволяет (из-за возможной инвалидации итератора). Но такие алгоритмы обычно итак пишут под unsafe в библиотеках. В прикладном коде не было пока необходимости.
Еще до выпуска 1.0 была идея у ссылок отдельно задавать мутабельность содержимого, и отдельно мутабельность самой ссылки. Можно было бы брать одну мутабельную ссылку на содержимое (но readonly саму ссылку) и несколько немутабельных на содержимое одновременно. Жаль что отказались, хотя синтаксис был бы сложнее.
Тут надо знать контекст, над чем он работал. Из его объяснения не понятно, откуда такие проблемы с временами жизни. Я видел кучу проблем при интеграции с другими языками, и там иногда проще обернуть в Rc или Arc. Но при работе над библиотекой на чистом Rust таких проблем не возникает.
Времена жизни обычно указываются явно в 2 случаях:
- взятие ссылки на поле/поля/слайсы
- оборачивание ссылки на исходный объект
В остальных случаях их опускают, и они вычисляются компилятором. При таком подходе не будет возникать "upwards of 30 or more statements depending on the complexity of your architecture".
С другой стороны, "This is because the borrow checker cannot run until an entire function compiles. Sometimes it seems to refuse to run until my entire file compiles." действительно встречается. Это мешает если не знаешь правила заимствования, и пишешь надеясь что компилятор тебя поправит. Не сказал бы что это проблема, в любом языке желательно "для того, чтобы быть высокопродуктивным программистом Rust ... нужно чтобы вы сделали все правильно с первого раза".
Однако и он пишет, что текущая ситуация в Rust гораздо лучше чем в С++:
в отличие от фразы выше "потому что Rust отстает по фичам от C++ лет на 15", которую плюсуют но не аргументируют.
ZeroVer это подвид SemVer.
То что вы вспоминаете, было еще до версии 1.0 языка. По определению, разработчики могли менять API до выхода 1.0.
В языке есть правила переполнения целочисленных типов, которые я привел выше. Вы же хотите чтобы стандартная библиотека им не следовала.
Если вы запустите в Release режиме, то будет нужное вам поведение (пруф). Так полезен метод или бесполезен?)
Ок, принято.
Может вы сможете привести список фич, помимо constexpr?
Пример в студию!
Сильное утверждение, доказать сможете?
Не встречал проблем с рефакторингом. Приведите пример.
Из фич знаю только вычисление в const expr, тут пока в Rust не очень. И это явно не 15 лет существует в С++.
По остальным я тоже могу привести фичи, в которых С++ отстает на N лет, и которых в нем никогда не будет.
Наспамили, работает, пишут новый код в ядре Linux. Это в вас говорит обида, что С++ в ядро не попал.
Это вообще традиция на Хабре, не зависимо от обсуждаемой темы. Хорошо что хоть некоторые так не делают.
Автор не знаком с концепцией ZeroVer. Она распространена не только в Rust, в Javascript например она никого не смущает.
std::ops::Try
доступен только в Nightly компиляторе, это "экспериментальная фича компилятора", которой никогда не было в Stable.Видимо автор всегда использует только нестабильные версии компиляторов в продакшен, ну ОК.
attempt to multiply with overflow
Ну да, в Debug режиме Rust обрабатывает переполнение как ошибку. И можно выключить черезoverflow-checks = false
, или включить аналогично в Release.Если нужно особое поведение, напиши его сам, и подай RFC на включение в стандартную библиотеку.
И т.д. Очень жирный наброс, ни одной нормальной претензии.
Ну и что будет делать старый код с новой версией? Этот подход вообще перпендикулярен "трюк с добавлением новых полей в структуру", т.к. на каждую версию пишется/генерится отдельный код парсинга.
Широкий круг задач тянет за собой кроссплатформенность, удобные системы сборки и прочий тулинг. С этим на С++ настолько плохо, что на Linux, Windows и Android он отдает свои позиции любым языкам с виртуальной машиной и сборкой мусора.
Остается достаточно узкий круг задач, в которых нужно максимальное быстродействие или близость к железу: игры, HighLoad, embedded.
Любой язык пытается заходить во все ниши. Например Javascript вышел из браузеров и потеснил Python в разработке оконных приложений. Не вижу в этом ничего плохого.
Rust тоже смог занять некоторые ниши, процитирую:
В микроконтроллерах, например, количество новых проектов на С++ стремится к 0, Rust уже соотносится с С примерно 30/70%. Сейчас ситуация такова, что найти библиотеку для датчика на Rust проще чем на С (это к вопросу о количестве крейтов).
В других же нишах, например десктопные приложения, Rust вероятно никогда не будет распространен.
Для enum и struct есть
#[non_exhaustive].
Но и в С нужно особым образом писать код, чтобы добавление нового поля ничего не сломало. Лучше использовать кодогенерацию для разных версий API.
Вот именно. Но в С и С++ почему-то в каждом HAL свой API.
Понимаю. По-этому под каждое семейство МК написана своя реализация embedded-hal, о чем я писал выше.
Прямо на сайте у них написано "750+ Boards Supported". Остальное не поддерживается, например любая кастомная плата. И в 99% случаев нужно делать именно кастомную плату.
А как же переиспользование кода? Разве не лучше чтобы было 1-2 драйвера на гитхабе, которые пилят всем миром. Чем 1-2 на каждое семейство МК (итого штук 20), каждый со своими багами.
Вы либо за "повторное использование кода", либо против, уж определитесь.
P.S. То что в мире С и С++ на МК не прижилось "повторное использование кода" не говорит о том, что сам принцип плох.
Нет жизни за пределами C/C++? А как же С#, Java или Go?
Всё таки пройдите по ссылке embedded-hal и прочитайте что это.
В драйвере нет никаких проверок на партномер МК, ему вообще без разницы какой МК, и даже без разницы архитектура ARM это или RISC-V.
Пример такого драйвера: https://github.com/copterust/mpu9250. Обратите внимание на зависимости. В них ничего нет про МК и библиотеки для него:
[dependencies]
embedded-hal = "0.2.3"
bitflags = "1.0"
libm = { version = "0.2.1", optional = true }
На с++ можно было бы сделать так:
1) абстрактный класс с методами работы с SPI например - аналог embedded-hal
2) реализацию этого класса для esp32 - аналог esp32-rs
3) драйвер для датчика MPU9250, в который передается ссылка на абстрактный класс из п.1
В итоге будет точно также один драйвер на любой МК и архитектуру.
За цать лет в С++ этого не сделали и, вероятно, уже никогда не сделают. Я не знаю, что помешало. Вероятно просто не смогли договориться про общий универсальный API.
А в Rust уже есть, работает, и пишется много нового кода (я не призываю переписывать старый код).
Есть другие реализации на С++, например Platformio. От производителей пишут на С для совместимости.
https://github.com/embassy-rs/embassy
Часть высокоуровневых библиотек вынесены в отдельные репозитории, например https://github.com/esp-rs/esp-hal
Как он может меняться, если эти библиотеки реализуют трейты из embedded-hal? Может вы вспоминаете давние времена, когда embedded-hal был версии 0.3?
Вы не правильно понимаете безопасность в Rust. Работа с прерываниями на любом языке будет unsafe по своей природе. Над этим кодом в любом языке надо писать safe обертку, и использовать её из прикладного кода. Rust просто дает инструменты, которые разделяют эти 2 этапа.
Мой пример из личной практики: драйвер тепловизора для async/await, работает по прерываниям через интерфейсы I2C и SPI. Запускал на Raspberry Pi4, кросскомпиляция на amd64 делается ровно 2 строчками (сравните с С и С++).
На весь проект 3 unsafe. Можно и без них, но будет лишнее копирование картинки. Впрочем возможно в std уже что-то завезли, и сейчас можно без unsafe.
Не знаю как там в OS. Но я, например, пишу под микроконтроллеры (stm32, esp32, nrf), которые на несколько ступеней ниже операционной системы. Весь код построен на прерываниях, DMA и RTOS.
Так вот, на Rust код превосходит С++ по всем параметрам: читаемость, безопасность, библиотеки, инструменты. Достаточно упомянуть простой факт: драйвер для конкретного датчика на Rust ровно один универсальный, подходит для любого семейства микроконтроллеров (см. https://github.com/rust-embedded/embedded-hal). На С++ же полный зоопарк.
Справедливости ради, год назад в библиотеках было похуже. И текущая отличная ситуация - это из-за отсутствия старого кода и обратной совместимости с ним.
Не вижу как проблемы OpenHarmony относятся к обсуждаемым языкам программирования. Просто ни одной ниточки их связывающей.
Вот только думать о владении и времени жизни гораздо проще для небольших кусков кода, а не для всех 100%. Ну если конечно вы не робот, который всё делает идеально. Я вот не робот.
Практика показывает, что компиляторы проверяют код лучше людей, и что ошибаются все люди, независимо от опыта.
Кроме того лучше написать чуть более медленный код, но в котором компилятор автоматически вставил проверки. А 95% тормозов убрать оптимизацией тех самых 5% кода.
В Android еще в 2022 году 21% нового нативного кода писали на Rust (сейчас больше). Тоже чтобы просто "вдохновлять людей"?
Да, я embedded и имел ввиду. Могу подтвердить, что память корутин лежит в preallocated storage, а куча вообще выключена.
С помощью некоторых ухищрений можно заставить компилятор вычислить максимальный размер памяти корутины, а не выдавать каждой по N байт. При этом стек общий и невозможно переполнением стека испортить чью-то память (на самом деле можно, но это уже вопрос того где линкер помещает статические переменные, и можно настроить так чтобы было нельзя).
Это вовсе не обязательно. Например Rust может хранить все данные на стеке, и у него stackless корутины.
Не обращайте внимания, странные люди везде есть.
В реальном коде все делают обертку на С (кодогенерацией с небольшими подпорками), над ней обертку на unsafe Rust (полностью автоматической кодогенерацией), и над ней обертку на safe Rust (вручную, но иногда часть удается автоматизировать). Вот хороший пример со всеми 3 уровнями оберток: https://github.com/twistedfall/opencv-rust.
А эти истории про переписывание из С++ на Rust скорее делают антирекламу, но по факту составляют примерно 0% кода.
Согласен, текущая реализация итераторов такое не позволяет (из-за возможной инвалидации итератора). Но такие алгоритмы обычно итак пишут под unsafe в библиотеках. В прикладном коде не было пока необходимости.
Еще до выпуска 1.0 была идея у ссылок отдельно задавать мутабельность содержимого, и отдельно мутабельность самой ссылки. Можно было бы брать одну мутабельную ссылку на содержимое (но readonly саму ссылку) и несколько немутабельных на содержимое одновременно. Жаль что отказались, хотя синтаксис был бы сложнее.