Комментарии 52
с переходом на безопасный язык разработчик будет вынужден тратить значительно больше времени на «борьбу с компилятором» - например, для описания графовых структур данных, сложных взаимных зависимостей
Не нравится - пишите unsafe
Не хотите переписывать - не переписывайте. А вот новые имеет смысл писать на Rust вместо Си
Если писать unsafe - тогда какой смысл перехода? Но я согласен насчет новых проектов без большой базы старого кода.
Смысл Rust для меня в выразительности языка больше чем в безопасности.
make illegal states unrepresentable => DDD, Domain Modelling Made Functional, Влашин, конечные автоматы и вот это вот всё (показывая рукой вокруг)
Рынок бизнес-логики на порядки больше чем системное программирование. Если Rust начнут выбирать не потому что "нужна безопасность памяти", а потому что "хочу чтобы компилятор проверял мою доменную модель" - это совсем другой масштаб adoption.
Но фишка в том, что “хочу чтобы компилятор проверял мою доменную модель” - это может быть реализовано и на других языках программирования, на том же самом C++.
Формально можно. Но сильно не удобно. И всё равно без гарантий
Но на практике разница огромная. В Rust это идиоматично и эргономично: enum с данными, match с проверкой exhaustiveness, Result/Option в стандартной библиотеке. Весь язык построен вокруг этого. В C++ std::variant - это неуклюжий визитор, а std::optional появился в C++17 и всё равно не заставляет тебя обрабатывать пустой случай.
Разница как между “можно” и “естественно”. На Java тоже можно писать функционально, но никто не скажет что Java - функциональный язык.
Если адаптация Rust пойдёт через DDD/бизнес-логику, а не через замену C в ядре Linux - это действительно другой масштаб. Но пока этого не происходит, порог входа слишком высок для типичного бэкенд-разработчика.
Если честно, то я не понял как связаны “поверка доменной модели” и std::variant c std::optional. Под проверкой доменной модели я понимаю что-то в этом духе:
#include <concepts>
struct UserId { int value; };
struct Email { const char* value; };
template<class T>
concept UserModel = requires(T u) {
{ u.id } -> std::same_as<UserId&>;
{ u.email } -> std::same_as<Email&>;
};
struct GoodUser {
UserId id;
Email email;
};
struct BadUser {
int id;
const char* email;
};
static_assert(UserModel<GoodUser>);
static_assert(!UserModel<BadUser>);
Это проверка структурного соответствия интерфейсу, не доменной модели.
Доменное моделирование по Влашину - это когда невалидные состояния невыразимы в системе типов. Иными словами parse, don't validate. Классический пример:
Заказ может быть в состоянии "черновик", "подтверждён" или "отправлен". У каждого состояния разные данные
enum Order {
Draft { items: Vec<Item> },
Confirmed { items: Vec<Item>, payment: Payment },
Shipped { items: Vec<Item>, payment: Payment, tracking: TrackingId },
}Невозможно иметь tracking без payment. Невозможно забыть обработать одно из состояний в match. Компилятор заставляет.
Concepts проверяют форму данных (validate) ближе к моменту использования, пропуская грязь в домен.
По сути та же проблема что с runtime-валидацией, только сдвинутая на этап компиляции. Лучше чем ничего, но грязь всё равно живёт в системе как легальный тип - BadUser существует, компилируется, может быть передан куда угодно где нет concept-ограничения. Один забытый requires - и он просочился.
Согласен, в этому случае, без std::variant или std::optional не обойтись.
enum EngineeringUnitCodes : uint8_t {
// Temperature
DegreesCelsius [[=TEMPERATURE, =u8"ºC"_sym ]] = 32,
DegreesFahrenheit [[=TEMPERATURE, =u8"ºF"_sym ]] = 33,
DegreesRankine [[=TEMPERATURE, =u8"ºR"_sym ]] = 34,
Kelvin [[=TEMPERATURE, =u8"K"_sym ]] = 35,
// Pressure
Pressure_1 [[=PRESSURE, =u8"inH2O"_sym ]] = 1, // inH2O | inches of water at 68ºF / 20ºC
Pressure_2 [[=PRESSURE, =u8"inHg"_sym ]] = 2, // inHg | inches of mercury at 0ºC
...
Pressure_238 [[=PRESSURE, =u8"inH2O (4ºC)"_sym ]] = 238, // inH2O (4ºC) | inches of water at 4ºC
Pressure_239 [[=PRESSURE, =u8"mmH2O (4ºC)"_sym ]] = 239, // mmH2O (4ºC) | millimeters of water at 4ºC
// Electromagnetic Unit of Electric Potential
MilliVolts [[=ELECTRIC_POTENTIAL, =u8"mV"_sym ]] = 36,
Volts [[=ELECTRIC_POTENTIAL, =u8"V"_sym ]] = 58,
// Electrostatic Unit of Current
MilliAmperes [[=CURRENT, =u8"mA"_sym ]] = 39,
// Electromagnetic Unit of Resistance
Ohms [[=RESISTANCE, =u8"Ω"_sym ]] = 37,
KiloOhms [[=RESISTANCE, =u8"kΩ"_sym ]] = 163,
//Miscellaneous
PotentialOfHydrogen [[=MISCELLANEOUS, =u8"pH "_sym ]] = 59, // (acidity or alkalinity)
Hertz [[=FREQUENCY, =u8"Hz"_sym ]] = 38,
...
PercentLowerExplosionLevel [[=MISCELLANEOUS, =u8"%LEL"_sym]] = 161,
PartsPerBillion [[=MISCELLANEOUS, =u8"ppb"_sym ]] = 169,
};C++26 с рефлексией по круче раста местами будет.
А рефлексия + контракты - это еще круче:
#include <concepts>
#include <meta>
#include <string_view>
#include <type_traits>
template <std::integral T, T Min, T Max>
struct bounded {
using domain_safe = void;
static constexpr bounded make(T v)
pre(v >= Min)
pre(v <= Max)
{
return bounded{v};
}
constexpr T get() const noexcept { return value; }
private:
T value;
constexpr explicit bounded(T v) : value(v) {}
};
template <std::size_t MaxLen>
struct non_empty_string {
using domain_safe = void;
static constexpr non_empty_string make(std::string_view s)
pre(!s.empty())
pre(s.size() <= MaxLen)
{
return non_empty_string{s};
}
constexpr std::string_view get() const noexcept { return value; }
private:
std::string_view value;
constexpr explicit non_empty_string(std::string_view s) : value(s) {}
};
template <typename T>
consteval bool is_domain_safe_type() {
using U = std::remove_cvref_t<T>;
return std::is_enum_v<U> || requires { typename U::domain_safe; };
}
template <typename T>
consteval void audit_external_input() {
template for (constexpr auto member : std::meta::nonstatic_data_members_of(^^T)) {
using member_t = [: std::meta::type_of(member) :];
static_assert(!std::is_pointer_v<member_t>);
static_assert(!std::is_reference_v<member_t>);
static_assert(is_domain_safe_type<member_t>());
}
}
enum class user_role { user, admin };
using age_t = bounded<int, 1, 150>;
using user_name_t = non_empty_string<64>;
struct CreateUserRequest {
age_t age;
user_name_t name;
user_role role;
};
struct BadCreateUserRequest {
int age;
char* name;
user_role role;
};
consteval void run_checks() {
audit_external_input<CreateUserRequest>(); // OK
// audit_external_input<BadCreateUserRequest>(); // compile-time error
}
Занятно, конечно. На практике это решается: code review + конвенция "голые примитивы запрещены в API-структурах" решает 95% проблем.
C++ конечно развивается. Но меня пугают монстры под диваном. Написать write only code не поддерживаемый и не понятный там шансов сильно больше чем в Rust
И вообще, я не пишу для прода на этих языках, только питомцев.
C++ разрешены неявные преобразования, перегрузки операторов,C++ define= текстовая подстановка, удачи с отладкой, непонятки кто владеет памятью
Надо базу исправлять, а потом уже хитромудрие чтобы не пускать через апи примитивы. На этой базе ошибок сильно больше
Вот особенно понравилось
// C++: реальный код из Boost.Spirit
auto parser = *(char_('a') >> +lit("hello") | eps(true) - eol);
// Что делает этот код? Без документации — удачи.
// * — Kleene star? Разыменование? Умножение?
// >> — парсерная композиция? Битовый сдвиг? Вывод в поток?
// | — альтернатива? Побитовое ИЛИ?
// - — разность? Вычитание?)
Ничего страшного, напишем руками
macro_rules! engineering_units {
(
$(
$(#[$doc:meta])*
$name:ident : $category:ident, $sym:expr, $code:expr
),* $(,)?
) => {
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EngineeringUnit {
$( $(#[$doc])* $name = $code, )*
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Category {
Temperature,
Pressure,
ElectricPotential,
Current,
Resistance,
Frequency,
Miscellaneous,
}
impl EngineeringUnit {
pub const fn symbol(&self) -> &'static str {
match self {
$( Self::$name => $sym, )*
}
}
pub const fn category(&self) -> Category {
match self {
$( Self::$name => Category::$category, )*
}
}
pub const fn from_code(code: u8) -> Option<Self> {
match code {
$( $code => Some(Self::$name), )*
_ => None,
}
}
pub const fn code(&self) -> u8 {
*self as u8
}
}
};
}
engineering_units! {
// Temperature
DegreesCelsius: Temperature, "ºC", 32,
DegreesFahrenheit: Temperature, "ºF", 33,
DegreesRankine: Temperature, "ºR", 34,
Kelvin: Temperature, "K", 35,
// Pressure
#[doc = "inches of water at 68ºF / 20ºC"]
InchesOfWater68F: Pressure, "inH₂O", 1,
#[doc = "inches of mercury at 0ºC"]
InchesOfMercury0C: Pressure, "inHg", 2,
#[doc = "inches of water at 4ºC"]
InchesOfWater4C: Pressure, "inH₂O (4ºC)", 238,
#[doc = "millimeters of water at 4ºC"]
MillimetersOfWater4C: Pressure, "mmH₂O (4ºC)", 239,
// Electric Potential
MilliVolts: ElectricPotential, "mV", 36,
Volts: ElectricPotential, "V", 58,
// Current
MilliAmperes: Current, "mA", 39,
// Resistance
Ohms: Resistance, "Ω", 37,
KiloOhms: Resistance, "kΩ", 163,
// Miscellaneous
#[doc = "acidity or alkalinity"]
PotentialOfHydrogen: Miscellaneous, "pH", 59,
// Frequency
Hertz: Frequency, "Hz", 38,
// Miscellaneous
PercentLowerExplosionLevel: Miscellaneous, "%LEL", 161,
PartsPerBillion: Miscellaneous, "ppb", 169,
}Паритет
Что будешь делать если лайфтаймы не согласованы?
Rust часто выбирают потому, что это тупо практично. Это хорошо видно в областях, где нужно быстро выкатить продукт высокой ответственности (финансы, крипта, скоростной трейдинг) – там в последние годы практически безальтернативен Rust в качестве языка нового проекта.
Смысл перехода на Rust именно в том, чтобы писать unsafe (там, где это надо) ☺. В C++ не надо писать unsafe только потому, что вся программа от начала до конца unsafe.
Тем, что кому не нравятся, перепишут ваш unsafe на safe и для этого им не понадобится держать в голове весь проект. Веник лучше ломается по тростиночке.
наплачутся еще разрабы с растом. "слезъми изойдут". вся эта суета и движуха не просто так затеяна.
Суета и движуха - это совпадение интересов нескольких заинтересованных групп: корпорации хотят снизить затраты на безопасность, государства используют регуляторные рычаги как повод для повышения защиты инфраструктуры, (что тоже пересекается с интересами корпораций, как исполнителями госзаказа), инфобизнес монетизируют хайп, а молодые разработчики используют эту поднятую волну для карьерного роста :-)
Конспирология какая-то. Язык Rust попросту очень хороший и приятный и люди на нём пишут с удовольствием – это не приходит в голову? И кстати, «спасибо» C++; типичная история, когда опытный C++-разработчик приходит в отчаяние от того, во что превратили этот некогда великий язык, смотрит по сторонам и переходит на Rust.
Никто не спорит, что в плюсах (и в си тоже) можно придерживаться хороших практик и количество UB, расстрелов памяти и всей прочей шелухи будет меньше (чем если не придерживаться). Проблема в том, что люди не хотят придерживаться хороших практик. Они хотят писать так, как им привычнее и удобнее. И компилятор плюсов, в отличии от раста, не даёт им по рукам, когда они в очередной расстреливают память или пихают данные в одну и ту же переменную из разных потоков без синхронизации.
И вот тут уже надо решать: либо потратить пару недель на воспроизведение, поиск и исправление бага в многопоточном коде гигантской плюсовой кодовой базы (даже с использованием санитайзеров!), либо получить удар по рукам от компилятора раста сразу при разработке. Плата за последнее - это 1) учить надо новый язык с новым синтаксисом, 2) привыкать к идеологии и ограничениям раста, 3) (самое сложное для многих) принять, что ты не умнее компилятора и что когда он даёт тебе по рукам, то он в 99% прав.
И вот тут уже надо решать: либо потратить пару недель на воспроизведение, поиск и исправление бага в многопоточном коде гигантской плюсовой кодовой базы (даже с использованием санитайзеров!), либо получить удар по рукам от компилятора раста сразу при разработке. …
Это хорошо только в тории, тогда как на практике Rust не гарантирует безопасную работу в многопоточном коде, так же как и С++. Поэтому вы точно так же будете тратить те же пару недель на его воспроизведение. Собственно об этом и была изначальная статья.
Rust реально гарантирует отсутствие data races на уровне компилятора (Send/Sync traits, borrow checker). Это не то же самое что “все многопоточные баги”, но data race - самый опасный и трудновоспроизводимый класс.
Что Rust НЕ предотвращает:
deadlocks
логические race conditions (например, check-then-act)
livelock
Эти баги действительно могут стоить недель отладки и в Rust тоже.
Но утверждение “так же как и C++” - неправда. В C++ data race это undefined behavior, и ты можешь месяцами не знать что он есть. В Rust ты просто не скомпилируешь код с data race без явного unsafe.
“Так же как в С++” - это отсутствие гарантий в данной области. Конечно в C++ и Rust это реализуется по разному из-за разного подхода и общей архитектуры “по умолчанию”.
В С++ действительно сперва придется прорабатывать архитектуру и API приложения, чтобы обезопаситься от data race на уровне типов, тогда как в Rust это изначально проверяется в самом компиляторе.
Rust реально гарантирует отсутствие data races на уровне компилятора (Send/Sync traits, borrow checker). Это не то же самое что “все многопоточные баги”, но data race - самый опасный и трудновоспроизводимый класс.
Что Rust НЕ предотвращает:
1,2,3
Гарантирует, но не предотвращает, это как? =)
Borrow checker просто запрещает любой мутабельный шаринг, в тч между потоками.
Send/sync - всего лишь предупреждает о необходимости не забыть атомики и костыль для BC, чтобы хоть что то компилятор разрешил сделать .
Итого - помощи с гулькин нос. А основные проблемы не решены.
Бонусом масса неудобств.
А мемори сефети проблем в большинстве современных языков уже нет.
Borrow checker заставляет вынести мутабельный шаринг в явные, проверенные примитивы. Прикладной код остается в safe Rust и BC его полноценно проверяет. Unsafe сконцентрирован в компактных проверенных библиотеках
А мемори сефети проблем в большинстве современных языков уже нет
Это те, что с GC? Смешно.
Гарантирует, но не предотвращает, это как?
У меня впечатление, что вы придуряетесь. Перечитайте
гарантирует отсутствие data races и не предотвращает ещё 3 других указанных проблемы
У меня впечатление, что вы придуряетесь. Перечитайте
гарантирует отсутствие data races и не предотвращает ещё 3 других указанных проблемы
проблемы того же класса. Тут гарантируем, а тут рыбу заворачивали.
С мемори сефети кстати то же самое - раст гарантирует не все случаи.
В любом случае, это не проблема. Проблема в избыточной рекламе и сноскам мелким шрифтом.
Удобно - пользуйтесь, но мне неудобно.
“пишите хорошо, не пишите плохо” Не. Такие советы не работают
Они хотят писать так, как им привычнее и удобнее. И компилятор плюсов, в отличии от раста, не даёт им по рукам, когда они в очередной расстреливают память или пихают данные в одну и ту же переменную из разных потоков без синхронизации.
Это полнейшая и беспросветная чушь! Так пишут только студенты младших курсов, потому что к старшим курсам они уже вдоволь настрелялись себе в ногу и стараются всеми силами так не делать! Ошибки с памятью (как и с доступом к общим ресурсам), которые возникают в сложных проектах, появляются там не из-за безалаберности программиста, а из-за внутренней сложности архитектуры проекта. Сложность как сущность является причниной большинства проблем. И тут важно то, что эти ваши "безопасные" языки ни как не помогают уберечься от проблемы сложности. Более того, они делают сложную систему еще более сложной и запутанной.
Но хайполовый от бигтеха ухватили беса за хвост и доят тему "безопасных" языков на полную катушку втюхивая тезис о дураках программистах которые не хотят писать как надо.
Ну так часто и не хотят же. И заявляют, что так и надо ибо им мало платят и потому они могут даже немножечко вредить =)
А бывают и не могут - не умеют
Я не знаю ни одного человека который бы умышленно выполнял свою работу плохо. Есть недостаточно опытные/квалифицированные люди которые в силу отсутствия знаний или опыта могут выполнить работу не достаточно хорошо. Есть ограничения по ресурсам и времени, которые сильно влияют на качество работы (и так бывает очень часто). Но я не втречал и не слышал о работниках, которые бы специально вредили, потому что им мало платят.
Полно людей, которые говорят, что им не нужно [высшее] образование, они и так [прекрасно] могут.
Следующая категория - "нам некогда писать идеальный код" и ещё 10 причин.
Это ровно Ваше 2е и 3е утверждения =)
Слово "безопасность" вообще достаточно далеко в настоящее время по смыслу от языков программирования, так как в существенно больше зависит от алгоритмов и контекста использования. То есть это больше про права доступа к определённой информации. В контексте языков программирования правильнее использовать слово "уязвимость" - использование огрехов в работе алгоритма/оборудования для вывода его из строя или получения неразглашаемой информации/её уничтожения. Учитывая что С++ поддерживается языковыми моделями достаточно хорошо, и разработка в целом довольно итеративное занятие, то выигрыш rust в каком-нибудь бенче "меньше уязвимостей в коде, сгенерированном одним промптом" не является сильно значимым преимуществом по моему мнению. Просто фактор личных предпочтений, эффекта "утёнка" и прочей психологии.
издержки от ошибок в ПО всегда ложатся на конечных пользователей, а не на разработчиков, поэтому у последних нет рыночного стимула к их полному устранению.
Вы описываете ситуацию с коммерческой разработкой. А ещё бывает внутрикорпоративная, когда из-за бага, уронившего прод, разработчику позвонят в 3 часа ночи. Это стимулирует.
Продукт, который активно развивается, естественным образом избавляется от старых ошибок - не потому, что кто-то целенаправленно их ищет, а потому, что переписывание улучшает его качество.
Не только улучшает, но и ухудшает. Статическая типизация, авто-тесты и borrow checker помогают бороться с добавлением новых косяков.
эта палка о двух концах, не верите проверьте, пишем приложение на ява свинг, пишем такое же на раст, иииииии где-то тут появится skills issue, в итоге если разраб понимает С++ он осознано начинает писать на плюсах, потомучто на Расте есть нюансы, но они не в тех цветах(тоесть не в той категории что типо раст плохой, или парадигмы плохие или что раст просто плохой) - суть в глубже раст показывает ошибки, понимая их, проще иногда действительно описать это на С++, чем пытаться фиксить то что нереально просто например работу модуля или библиотеки, просто потомучто в С так и разраб знает это, а раст со своими связями уже чото там другое делает и находясь в такой точке просто проще писать на С++, в этом проблема. Но и тут получается у Раста четко очерчено использование - где-то на сухом остатке это что-то либо чистое, либо библиотек или модуль чистые не текут и прочее, почему? чтобы не писать велосипеды, а сам раст прекрасен как и хаскель )
Rust не плохой, он просто честный. Он заставляет платить за безопасность временем разработчика еще на этапе написания первой строчки, а не во время отладки сегфолтов в полночь.
тоесть честность, многопоточность крутые, но есть моменты которые просто проще на С/С++, просто например потомучто библиотека не на ффи в С, а именно на С.
например, проще прокинуть бинды через модуль уже на С++23/26 уже где-то чем биться с библиотекой раста например.
Rust не плохой, он просто честный. Он заставляет платить за безопасность временем разработчика еще на этапе написания первой строчки, а не во время отладки сегфолтов в полночь.
Это еще ладно. Хуже когда падение случилось на проде. Упало не то что дорабатывали, а где-то на пару уровней ниже по стеку вызовов (и где-то даже не в продуктовом коде, а в системной API), а доступа до прода нет и воссоздавать картину приходится по дампам и джоблогам... Т.е. интуитивно понятно что кто-то где-то не туда в память залез, но визуально по коду абсолютно непонятно где именно, а на тестовой среде оно вообще не воспроизводится...
И возникают шальные мысли что все вот это проще переписать заново чем искать ошибку.
А если еще тебе намекают что цена вот этой конкретной ошибки исчисляется суммой с многими нулями чужих денег...
согласен такой момент есть, я на своём не експертном месте, столкнулся с ситуацией, взять нашу ветку, чистый код, терминал и владение,
вот на клоне майнкрафта на расте я просмотрел, что такое владение в полной мере многопоточно бесконечно, но без сети. тут GL(кусок памяти на видеокарте, шардированый алокатор на слотах, многопоточка на каналах mpsc всего цикла от генерации до отправки на карточку, со своим "сборщиком мусора по флагу dirty" )
а например в терминале, если хотеть не GL, после многопоточного владения как в игре вокселей, если сначала делаешь на canvas и Rect - просто тут по каким-то причинам уже не знаю усадка это контекста - как пишут друзья какие-то или это легитимная утечка - искать корень утечки это гемор, и решение это как раз то самое GL - честное владение! - тоесть придётся писать это владение памятью на GL, с флагом dirty тоже наверно.
и да с таким вопросом по известным причинам по терминалу не столкнулся на Java 25, в этом что-то есть, тут получилось самый бронебойный терминал, но не хватает 1мс скорости условно )
знаю что на С/С++ от ректов или контекста не течет ничего на квадратиках, поэтому модули на С++23/26 для меня как глоток свежего воздуха воспринимается как новая фича, которой хочется пользоваться )
Уже давно доказана эффективность rust, ваша заметка опоздала на лет на 5
Можно ли утверждать, что, согласно вашей статье, Rust - это заплатка со стороны разработки, призванная решить проблему плохой аналитики и архитектуры проекта?
В вашем вопросе скрыт подвох за счет использования негативной коннотации в самой формулировке. Но я все равно попробую вам ответить нейтрально.
Rust хороший проект и собственным примером показал замечательный подход к обеспечению безопасной разработки программного обеспечения. Но не в части безопасного управления памятью (где у него остаются некоторые проблемы), а в смене принципиальной парадигмы обеспечения безопасности на уровне исходного кода, когда безопасность реализуется на основе гарантий самого языка программирования.
Нет. Как раз эти проблемы Раст, как и большинство других ЯП, не решает. Это не языковые проблемы
мнение в виде краткой стати с описанием фундаментальной экономической модели разработки ПО, которая не способствует (и объективно не должно способствовать) массовому переходу с C/C++ на «безопасные» альтернативы. Так как из-за особенностей распределения затрат у разработчика ПО отсутствует экономическая мотивация
Правильный посыл, но неполные и потому делаются неверные выводы.
Ведь платить можно не только безопасностью.
Классический треугольник - время [разработки] - качество (безопасность, скорость работы) - стоимость (сложность разработки, ресурсы эксплуатации)
Итого навскидку список, вероятно тоже неполный - можно расширять
Платим памятью - и приходим к Яве или Шарпу
Платим скоростью исполнения - Питон, Руби
Платим сложностью и временем разработки - С++, Раст
Итп
Интересная идея.
В Rust работа над качеством/безопасностью кода сдвигается на более ранний этап, в прототип программы. Нет вариантов сделать MVP, не потратив время на качество кода. С этой стороны, Rust менее пригоден для продуктовой разработки, по крайней мере, на начальном этапе.
С другой стороны, код-решето даже в прототипе создаст массу проблем, и на их исправление времени уйдёт вероятно даже больше, чем было бы потрачено на борьбу с компилятором Rust. В пользу этой точки зрения можно зачесть тот факт, что Rust стабильно побеждает на ICFP Programming Contest.
Причем трудоемкость исправления ошибок, которые теоретически устраняются переходом на memory-safe языки, составляет единицы процентов от общего бюджета разработки
Что насчёт трудоёмкости поиска этих ошибок?
Rust - это лишь ещё один инструмент, не стоит его рассматривать как что-то обязательное. Бороться с компилятором - это глупая затея (в любом случае проиграешь), есть определённые "правила" написания кода, в Rust они просто более строгие, чем в других языках. Как итог из всех итераций реализации проекта должен получиться готовый продукт, который бы отвечал всем требованиям заказчика. В случае, если вы можете обеспечить это на языке(инструменте), который считается наиболее удобным и знакомым, то на нем и следует писать.

Экономика безопасности кода или почему Rust не нужен