До JUnit в Java ещё пока не дотягивает, на мой взгляд. Например, не хватает возможности удобно писать data-driven тесты. Так, чтобы 1 файл с данными = 1 тест, например; с возможностью запуска индивидуальных тестов.
Какие-то движения вокруг custom test frameworks вроде происходят, но, к сожалению, нет времени, чтобы во всём этом разобраться.
Пока выкручиваюсь с proc macro, который сканирует файлы и создаёт #[test] функцию на каждый файл, выглядит примерно так (второй параметр — регулярное выражение для матча на «основной» файл теста, остальные параметры — шаблоны-подстановки по этому регулярному выражению):
Можно, конечно, сделать так, чтобы все эти тесты в одной #[test] функции выполнялись, но тогда очень неудобно с этими тестами работать. Например, если делается рефакторинг и половина тестов поломана — хочется возможности запустить один тест, чтобы начать с чего-то мелкого. Плюс, хочется видеть все поломки, а не первую. И так далее.
Может быть, можно как-то через кишки модуля test выкрутиться?..
P.S. Похоже, можно попробовать через «harness = false» в Cargo.toml. Пора снова переписывать фреймворк :D
P.S. Не идеален, но работает сносно (у нас проект на ~100 KLOC строк кода). Некоторые функции работают очень медленно (например, «найди мне все реализации этого типажа»).
Писал в основном на Java более 10 лет (другие языки использовал наскоками, по мере необходимости).
В принципе, на Java я ООП использовал весьма ограничено. Предпочитал отделять данные от функций, их обрабатывающих, неизменяемые структуры данный, стиль с легким налетом функционального программирования, минимум наследования. С этой частью проблем особо не было при переходе на Rust.
С владением в Rust поначалу было непривычно, но процентов 80, наверное, освоил довольно быстро (простые случаи). Более глубокое понимание на уровне интуиции, наверное, выработалось где-то через полгода, особенно после разработки нескольких вариантов API типа reflection в Java (обобщенный обход данных заданных некоторой схемой).
Из необычного было то, что мы используем Rust совсем не как системный язык программирования, а для самого что ни на есть энтерпрайза, фактически как аналог Java / .NET. Поэтому, сложно сказать, какая часть трудностей из-за языка как такового, а какая — из-за особенностей его применения. Часть сложностей была, возможно, из-за недостатка опыта (у меня) в обоих областях: и в знании Rust, и в знании предметной области.
Приходилось проявлять, гм, смекалку. Даже, не побоюсь признаться, приходится заимствовать некоторые подходы из Java (что порой вызывает удивление коллег).
С точки зрения «разгона» команды, старт у нас был довольно трудным, на мой взгляд, — Rust довольно сложный язык, особенно как второй язык после JavaScript. Но постепенно ситуация исправляется, сейчас у нас примерно человек 8 пишущих активно на Rust. Плюс нам повезло нанять пару человек, имеющих активное участи в Rust экосистеме (в том числе и с позиции менторинга), поэтому я настроен оптимистично :)
У меня нет опыта разработки и поддержки больших систем на динамически типизированных языках (Java отнесём в категорию «условно статически-типизированных»), но пока что наш код на Rust переживает рефакторинги весьма успешно.
Эти маршруты действительно красиво выглядят в документации (и в подходящих проектах, наверное), но у нас в итоге всё свелось к фактически одному маршруту на всё подряд (ну, к 4-ем, GET/POST/PUT/DELETE), а дальше мы запрос сами разбираем… (тут возникает справедливый вопрос, зачем нам Rocket, но просто пока времени нет всё на hyper переделать).
Но всё же для разных задач — разные инструменты. Охотно допускаю, что где-то такие маршруты (и Rocket) могут быть удобны. Для более фиксированных API (наша проблема в том, что API очень уж динамичные).
Я год назад делал маленький проект, было плюс-минус без костылей (хотя, наверное, xargo можно за костыль посчитать). Всё компилировалось, прошивалось, отлаживалось, под STM32. По-моему, даже прямо из CLion-а у меня получалось отлаживать, но это не точно. gdb точно работал (или lldb? один из них :) ).
Но там всё постоянно меняется — это один из рисков. С тех пор они там какие-то HAL-ы напилили — про это ничего не знаю.
Моя оценка примерно такая:
1) Для хобби — сойдёт (проекты относительно маленькие), если есть желание. Мне понравился мой опыт, причём это был мой первый проект на Rust.
2) Для новых крупных проектов — если есть желание рискнуть, можно сделать ставку на Rust. Я слышал разные страшные истории от одного разработчика в одной всем известной компании про то, как у них низкоуровневый софт пишется (Raspberry PI, хотя это уже не совсем «микроконтроллер») — Rust бы там, как мне кажется, пришёлся бы в тему.
3) Для средних проектов — не знаю, там, наверное, проще взять готовые библиотеки. Хотя с другой стороны, Rust хорошо встраивается в C, наверное, можно кусочно на Rust делать (но FFI — это вдвойне опасная тема).
Я как-то задумался и полетел вперед наехав на лежачего полицейского. Ни о каком там пробежать даже и речи не шло, приземлился на колени/локти/запястья. Может, конечно, можно как-то натренировать навык спрыгивать и бежать, но без тренировки как-то не вышло…
Это в хорошем варианте. У меня все было не так гладко и с медицинскими последствиями.
Не серьёзными, но все же. Вроде подвернутой лодыжки и ушиба локтя, что я рукой двигать две недели не мог. И это в полной защите (видимо, фиговой) после падений на спину.
Где-то в сумме две недели ушло (4 раза по часу, но с перерывами на лечение).
Я встречал истории, и месяцами осваивают.
Самокат только чуть-чуть пробовал, там вроде вообще ничему учиться не надо (ну может скорость подбирать, да на дорогу смотреть).
Не-не-не, погоди. Я допустил ошибку, не указав явно, что мысленный эксперимент я предлагаю ограничить исключительно строками (так как про них разговор и шёл).
Во-первых, разделяемое владение и исключительное владение я убирать не предлагаю. Это, разумеется, семантика, а не низкоуровневая деталь.
В-вторых, RAII я тоже убирать не предлагаю — это тоже семантика.
Но конкретно для строк, оба этих пункта неприменимы. Ресурсов никаких за ними нет, кроме памяти, которая по условиям мысленного эксперимента освобождаться будет моментально с уходом последней ссылки. Разделяемые изменяемые строки я тоже не предлагаю.
То есть, никаких новых ошибок бы не было.
Проблема только в том, что такого «оракула» не существует — и именно поэтому мы и имеем некоторое постоянное неудобство при работе со строками (но получаем и преимущества).
Я, кстати, не совсем согласен с этой точкой зрения. У Rust довольно большой потенциал в прикладных задачах. Он безопасный, с адекватной эргономикой и достаточно большими возможностями в плане построения абстракций (товарищи хаскеллисты, не бейте, пожалуйста!).
При этом по законам материалистической диалектики, возможен переход от количества к качеству.
Наприер, когда твое приложение «само по себе» работает в N раз «быстрее» аналогичного на Java, это открывает дополнительные возможности. Там, где была bulk обработка вдруг оказывается целесообразной реалтайм обработка, там где нужен был кластер, можно обойтись одним узлом. Я знаю компании, которых разорили счета с AWS. Можно невооруженным взглядом наблюдать проблемы производительности, например, на мобильных устройствах. Я наблюдал постепенное угасание производительности систем на Java.
Спасёт ли Rust от этого? Не знаю, но у него хороший задел.
При этом традиционный аргумент против таких языков был «дорого обходится разработка», из-за безопасности, из-за большей сложности управления памятью, и.т.д. Rust, на мой взгляд, довольно хорошо поднимает планку эргономичности разработки и при этом всё-равно оставляет возможность залезть поглубже и применить суперспособности типа (таких).
Значит ли это, что надо RIIR? Нет. Но совсем откидывать Rust, как «системный язык» тоже не стоит.
Да, но почему тебе интересно владение само по себе?
Если бы был некий магический оракул, который бы убивал объекты моментально с уходом последней ссылки и не имел бы всех недостатков GC или подсчёта ссылок (таких как расход памяти на метаданные, давления на кеш в случае GC), ты бы стал заморачиваться с владением?
Или бы просто сделал так, что String — это изменяемая строка, а &str — неизменяемая, которую можно передавать куда угодно и как угодно хранить (считай, аналог Rc но без расходов на подсчет ссылок).
Всё было бы гораздо проще, если бы std::borrow::Borrow borrow() возвращал бы Borrowed, а не &Borrowed.
У нас сейчас вариант с Cow, на самом деле, потому что плюс-минус пофиг.
Просто человек вот удивлялся, когда это дескать в Rust низкоуровневые вещи постоянно приходится указывать — я говорю, что постоянно, особенно со строками. Я считаю, что с точки зрения логики прикладной программы все эти жонглирования особо смысла не имеют. С точки зрения корректности программы на Rust — разумеется, с точки зрения производительности — да, конечно.
Да я в общем-то и не считаю, что это проблема. Я на автомате такое пишу, новичкам в большинстве случаев компилятор внятно подскажет, а где не подскажет компилятор, там я помогу :)
Но если уж быть честным, то надо признать, что в Rust довольно много вот таких «ритуальных» действий (хотя их количество и сокращают — например, RFC 2005).
Тем не менее, это постоянно вылезает, о чём и был мой комментарий. Компилятор, конечно же, помогает, но это не убирает тот факт, что такие низкоуровневые моменты (потому что к бизнес логике они имеют слабое отношение) нужно указывать постоянно.
Даже если забить болт на оптимальность, всё равно, в применении к строкам, будут постоянные to_string/clone и довольно неуклюжие операции со строками (впрочем, тут format! сильно помогает).
Понятно, что пример совершенно искусственный.
Тут есть небольшая проблема, если я правильно понимаю,
&FooBar
привести к&Foo
не получится.Например, если
run_foo
вдруг захочет вызвать другую функцию, которой нужен&Foo
. Или вернуть&Foo
в составе какой-то структуры.В первом случае ещё можно выкрутиться как-то так (ну или через newtype):
А вот вернуть из функции
&Foo
, если дан&FooBar
уже никак (впрочем, решается тривиально через добавление функцииfn as_foo(&self) -> &Foo
наFooBar
).До JUnit в Java ещё пока не дотягивает, на мой взгляд. Например, не хватает возможности удобно писать data-driven тесты. Так, чтобы 1 файл с данными = 1 тест, например; с возможностью запуска индивидуальных тестов.
Какие-то движения вокруг custom test frameworks вроде происходят, но, к сожалению, нет времени, чтобы во всём этом разобраться.
Пока выкручиваюсь с proc macro, который сканирует файлы и создаёт #[test] функцию на каждый файл, выглядит примерно так (второй параметр — регулярное выражение для матча на «основной» файл теста, остальные параметры — шаблоны-подстановки по этому регулярному выражению):
Можно, конечно, сделать так, чтобы все эти тесты в одной #[test] функции выполнялись, но тогда очень неудобно с этими тестами работать. Например, если делается рефакторинг и половина тестов поломана — хочется возможности запустить один тест, чтобы начать с чего-то мелкого. Плюс, хочется видеть все поломки, а не первую. И так далее.
Может быть, можно как-то через кишки модуля test выкрутиться?..
P.S. Похоже, можно попробовать через «harness = false» в Cargo.toml. Пора снова переписывать фреймворк :D
P.S. Не идеален, но работает сносно (у нас проект на ~100 KLOC строк кода). Некоторые функции работают очень медленно (например, «найди мне все реализации этого типажа»).
В принципе, на Java я ООП использовал весьма ограничено. Предпочитал отделять данные от функций, их обрабатывающих, неизменяемые структуры данный, стиль с легким налетом функционального программирования, минимум наследования. С этой частью проблем особо не было при переходе на Rust.
С владением в Rust поначалу было непривычно, но процентов 80, наверное, освоил довольно быстро (простые случаи). Более глубокое понимание на уровне интуиции, наверное, выработалось где-то через полгода, особенно после разработки нескольких вариантов API типа reflection в Java (обобщенный обход данных заданных некоторой схемой).
Из необычного было то, что мы используем Rust совсем не как системный язык программирования, а для самого что ни на есть энтерпрайза, фактически как аналог Java / .NET. Поэтому, сложно сказать, какая часть трудностей из-за языка как такового, а какая — из-за особенностей его применения. Часть сложностей была, возможно, из-за недостатка опыта (у меня) в обоих областях: и в знании Rust, и в знании предметной области.
Приходилось проявлять, гм, смекалку. Даже, не побоюсь признаться, приходится заимствовать некоторые подходы из Java (что порой вызывает удивление коллег).
С точки зрения «разгона» команды, старт у нас был довольно трудным, на мой взгляд, — Rust довольно сложный язык, особенно как второй язык после JavaScript. Но постепенно ситуация исправляется, сейчас у нас примерно человек 8 пишущих активно на Rust. Плюс нам повезло нанять пару человек, имеющих активное участи в Rust экосистеме (в том числе и с позиции менторинга), поэтому я настроен оптимистично :)
У меня нет опыта разработки и поддержки больших систем на динамически типизированных языках (Java отнесём в категорию «условно статически-типизированных»), но пока что наш код на Rust переживает рефакторинги весьма успешно.
Ничего хорошего в нём нет, но приходится делать, то, что приходится :)
Эти маршруты действительно красиво выглядят в документации (и в подходящих проектах, наверное), но у нас в итоге всё свелось к фактически одному маршруту на всё подряд (ну, к 4-ем, GET/POST/PUT/DELETE), а дальше мы запрос сами разбираем… (тут возникает справедливый вопрос, зачем нам Rocket, но просто пока времени нет всё на hyper переделать).
Но всё же для разных задач — разные инструменты. Охотно допускаю, что где-то такие маршруты (и Rocket) могут быть удобны. Для более фиксированных API (наша проблема в том, что API очень уж динамичные).
Мы, кстати, энтерпрайз кровавее некуда (healthcare) на Rust делаем (Commure). С чистого листа.
Но там всё постоянно меняется — это один из рисков. С тех пор они там какие-то HAL-ы напилили — про это ничего не знаю.
Моя оценка примерно такая:
1) Для хобби — сойдёт (проекты относительно маленькие), если есть желание. Мне понравился мой опыт, причём это был мой первый проект на Rust.
2) Для новых крупных проектов — если есть желание рискнуть, можно сделать ставку на Rust. Я слышал разные страшные истории от одного разработчика в одной всем известной компании про то, как у них низкоуровневый софт пишется (Raspberry PI, хотя это уже не совсем «микроконтроллер») — Rust бы там, как мне кажется, пришёлся бы в тему.
3) Для средних проектов — не знаю, там, наверное, проще взять готовые библиотеки. Хотя с другой стороны, Rust хорошо встраивается в C, наверное, можно кусочно на Rust делать (но FFI — это вдвойне опасная тема).
Не серьёзными, но все же. Вроде подвернутой лодыжки и ушиба локтя, что я рукой двигать две недели не мог. И это в полной защите (видимо, фиговой) после падений на спину.
Где-то в сумме две недели ушло (4 раза по часу, но с перерывами на лечение).
Я встречал истории, и месяцами осваивают.
Самокат только чуть-чуть пробовал, там вроде вообще ничему учиться не надо (ну может скорость подбирать, да на дорогу смотреть).
Во-первых, разделяемое владение и исключительное владение я убирать не предлагаю. Это, разумеется, семантика, а не низкоуровневая деталь.
В-вторых, RAII я тоже убирать не предлагаю — это тоже семантика.
Но конкретно для строк, оба этих пункта неприменимы. Ресурсов никаких за ними нет, кроме памяти, которая по условиям мысленного эксперимента освобождаться будет моментально с уходом последней ссылки. Разделяемые изменяемые строки я тоже не предлагаю.
То есть, никаких новых ошибок бы не было.
Проблема только в том, что такого «оракула» не существует — и именно поэтому мы и имеем некоторое постоянное неудобство при работе со строками (но получаем и преимущества).
Вот, кстати, ещё пример неудобства: users.rust-lang.org/t/extending-lifetime-to-the-whole-function-body/15440
Тоже не особо редкий случай, в общем-то.
При этом по законам материалистической диалектики, возможен переход от количества к качеству.
Наприер, когда твое приложение «само по себе» работает в N раз «быстрее» аналогичного на Java, это открывает дополнительные возможности. Там, где была bulk обработка вдруг оказывается целесообразной реалтайм обработка, там где нужен был кластер, можно обойтись одним узлом. Я знаю компании, которых разорили счета с AWS. Можно невооруженным взглядом наблюдать проблемы производительности, например, на мобильных устройствах. Я наблюдал постепенное угасание производительности систем на Java.
Спасёт ли Rust от этого? Не знаю, но у него хороший задел.
При этом традиционный аргумент против таких языков был «дорого обходится разработка», из-за безопасности, из-за большей сложности управления памятью, и.т.д. Rust, на мой взгляд, довольно хорошо поднимает планку эргономичности разработки и при этом всё-равно оставляет возможность залезть поглубже и применить суперспособности типа (таких).
Значит ли это, что надо RIIR? Нет. Но совсем откидывать Rust, как «системный язык» тоже не стоит.
Как-то так.
Если бы был некий магический оракул, который бы убивал объекты моментально с уходом последней ссылки и не имел бы всех недостатков GC или подсчёта ссылок (таких как расход памяти на метаданные, давления на кеш в случае GC), ты бы стал заморачиваться с владением?
Или бы просто сделал так, что String — это изменяемая строка, а &str — неизменяемая, которую можно передавать куда угодно и как угодно хранить (считай, аналог Rc но без расходов на подсчет ссылок).
У нас сейчас вариант с Cow, на самом деле, потому что плюс-минус пофиг.
Просто человек вот удивлялся, когда это дескать в Rust низкоуровневые вещи постоянно приходится указывать — я говорю, что постоянно, особенно со строками. Я считаю, что с точки зрения логики прикладной программы все эти жонглирования особо смысла не имеют. С точки зрения корректности программы на Rust — разумеется, с точки зрения производительности — да, конечно.
Но если уж быть честным, то надо признать, что в Rust довольно много вот таких «ритуальных» действий (хотя их количество и сокращают — например, RFC 2005).
Даже если забить болт на оптимальность, всё равно, в применении к строкам, будут постоянные to_string/clone и довольно неуклюжие операции со строками (впрочем, тут format! сильно помогает).