Pull to refresh

Comments 261

{a} + {b} + {c}

3 года уже как можно

// Гипотетический макрос
multi_fn! {
    fn handle(Locked, ButtonPress(_)) => keep_state(Err("invalid")),
    fn handle(Open, Lock) => transition(Locked, Ok(())),
    fn handle(_, Timeout) => keep_state(Ok(())),
}

// Развернётся в:
fn handle(state: State, event: Event) -> Transition {
    match (state, event) {
        (Locked, ButtonPress(_)) => keep_state(Err("invalid")),
        (Open, Lock) => transition(Locked, Ok(())),
        (_, Timeout) => keep_state(Ok(())),
    }
}

Можно самому написать макрос

О как. Спасибо.

Да ну? Я вот попробовал, есличё.

❯ cargo run --example counter
   Compiling joerl v0.3.0 (/opt/Proyectos/Rust/joerl/joerl)
error: invalid format string: expected `}`, found `.`
  --> joerl/examples/counter.rs:23:36
   |
23 |                     println!("[{ctx.pid()}] Count incremented to: {self.count}");
   |                                -   ^ expected `}` in format string
   |                                |
   |                                because of this opening brace
   |
   = note: if you intended to print `{`, you can escape it using `{{`

error: could not compile `joerl` (example "counter") due to 1 previous error

Не знаю, как ссылку дать туда

Вычисление в {}?

Может и не сработает. ХЗ

Так нельзя (тоже нахожу это неудобным):

println!("[{ctx.pid()}] Count incremented to: {self.count}");

Так можно:

println!("{a} + {b} + {c}");

Да, я уже понял и сказал, что это позорище.

Ну потом доделают. Я когда писал плагин строковой интерполяции (не к Русту), тоже думал, забить на выражения или всё-таки вставить. Вставил — народ пользуется самым простым: строками без выражений.

народ пользуется самым простым

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

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

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

И вот тут у нас включается элемент искусства, то самое чутьё художника, которое говорит — вот тут стоп.

Да, с моей точки зрения, подтверждённой моими действиями, они остановились слишком рано.

Всё так, да.

Довольно часто пользуюсь поддержкой произвольных выражений у Data.String.Interpolate из interpolate.

Необорот, это очень хорошее решение. Вставлять произвольный код в форматную строку? И потом ломать глаза, выясняя, куда это там автор забекдорил непонятные вычисления? Если уж прям невмоготу, то можно так:

println!("[{pid}] Count incremented to: {count}",
         pid = ctx.pid(),
         count = self.count,
);

Хорошее, хорошее, конечно, я с фанатиками, которых всё устраивает (если не всё — покажите ваши коммиты в корку раста) — не вижу смысла дискутировать.

"{obj.name}" - выражение. Если делать поддержку выражений, то пришлось бы сделать поддержку {obj.method().field + 1}, а для этого макросу нужно было бы поддерживать заимствования и, наверное, async/await, и ещё кучу всего. Возможно, разрабы решили, что оно того не стоит. Обсуждение на гитхабе: https://github.com/rust-lang/rust/issues/96999

Это просто смешно. И это не обсуждение, простите уж.

Макросу ничего специально поддерживать не нужно. println! — уже макрос, если он умеет понимать "{}", foo.bar() то научить его понимать "{foo.bar()}" через перекомпоновку параметров и вспомогательный приватный макрос, который уже написан. — задача для джуна на три часа.

Но можно так:

#[macro_use]
extern crate fstrings;

f!("[{ctx.pid()}] Count incremented to: {self.count}");

Обалдеть! Ради рабочей интерполяции строк — тащить в проект левую библиотеку! Что дальше? Для арифметических операций над числами мне тоже потребуется сторонний крейт?

А по мне это отличный подход. Делает сам язык более мелким и понимаемым, стимулирует конкуренцию между разными подходами к подобным фичам, и так далее. Минусы — надо дописать зависимость в package.yaml (или что там у растоманов). Ужас!

Это был бы отличный подход, если бы в корке не было интерполяции вовсе. Но она есть.

println!("[{pid}] Count incremented to: {count}", pid = ctx.pid(), count = self.count);

Охренеть удобно, конечно. Ровно то, что я искал: не только надо индексы считать, но еще и локальные присвоения парсить (я уж молчу про память туда-сюда насиловать ради ничего).

Так индексов нет.

я уж молчу про память туда-сюда насиловать ради ничего

Какая память?

Локальные переменные должны где-нибудь разместиться.

Да, это я в пылу спора до зеленых соплей договорился уже.

Ага, мы тут все периодически — «горячие финские парни»! :-)

Ну цель строковой интерполяции — видеть куда что вставляется без необходимости куда-то там глазами бегать.

Никак не привыкну, что советы тут дают одни диванные теоретики.

Можно самому написать макрос

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

Как немного MLер, мне Эрланговский синтаксис не нравится, то что в Rust почти 1:1 Haskell/ML. И имплементация класса кусочками хз где тоже не нравится. Предложенный синтаксис совершенно зря напрягает компилятор. Опять, же легко накосячить с байндигами(паттернами) и в лучшем случае словить ошибку компилятора, а в худшем часть случаев улетит в _ -> ... ветку по умолчанию.

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

Это макросы. Они так парсят

Глупость, добавить подстановки _tempXXX=expr вообще не должно быть проблемой. Как справедливо сказал ТС, дайте доступ к АСТ и antiquotes. Лисперы смотрят на вас как на... (Впрочем, адекватные макросы в Окамл тоже не сразу завезли, может и переделают).

Они не могут дать доступ к AST, потому что он прикручен сбоку. unquote нет не потому, что им жалко, а потому, что для unquote нужен честный AST, которым оперирует компилятор.

Понимаете, он очень сложен, этот AST. Вот я давеча ковырял PPX, там масса разных наслоений в том же OCaml:

let f = fun a b c -> ...
let f a b = function ... -> ... | ... -> ...
let f a b c = ...

Заметьте, три разных варианта определения функции. Все три на уровне Parsetree (это не AST) требуют совершенно разной обработки. А ведь это совершенно базовая штука, это Core ML — 40 страниц описания, см «Введение в Standard ML» Харпера.

То есть, метапрограммирование нужно органично вставлять в язык, а не прикручивать через 25 лет, как это сделал ув Alain Frisch.

Почти все недовольны макросами.

«Почти все» из какой именно выборки? Какие проекты? Какие страны?

Кроме того, я не «недоволен» же, я просто вижу, как можно было бы лучше, если бы AST не прикручивали сбоку, но сейчас уже поезд ушел.

Всё прогнило
Всё прогнило

AST

"Код = данные" это уязвимость. В Lisp (eval (read)) — прямой путь к code injection. Хотя, если только compile time... Ну, ок, соглашусь.

Токио асинк тоже макрос

#[tokio::main]
async fn main() {
    foo().await;
}

// Развернётся примерно в:
fn main() {
    tokio::runtime::Runtime::new()
        .unwrap()
        .block_on(async { foo().await; })
}

Все довольны.

Коллега говорил про разработчиков. В случае токио надо спрашивать разработчика токио.

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

#[gen_statem(fsm = r#"
    [*] --> locked
    locked --> |coin| unlocked
    locked --> |push| locked
    unlocked --> |push| locked  
    unlocked --> |coin| unlocked
    unlocked --> |off| [*]
"#)]

Я тут разбираю codegen велосипеды 10 летней давности и мне совсем не весело.

Если хорошо написано как в Токио, то ок. Если самопал то ну его нафиг.

fn handle(Locked, ButtonPress(_)) => keep_state(Err("invalid")),
fn handle(Open, Lock) => transition(Locked, Ok(())),
fn handle(_, Timeout) => keep_state(Ok(())),

Есть ещё typestate с явными типами и переходами

struct Locked;
struct Open;

enum State {
    Locked(Locked),
    Open(Open),
}

impl Locked {
    fn button_press(self, _digit: u8) -> (Locked, Result<(), &'static str>) {
        (self, Err("invalid"))
    }

    fn timeout(self) -> (Locked, Result<(), &'static str>) {
        (self, Ok(()))
    }
}

impl Open {
    fn lock(self) -> (Locked, Result<(), &'static str>) {
        (Locked, Ok(()))
    }

    fn timeout(self) -> (Open, Result<(), &'static str>) {
        (self, Ok(()))
    }
}

Длинно, конечно

почему ссылки &foo — не поведение по умолчанию?!

Потому что не все типы ссылочные, есть еще и типы значений(i32, bool, структуры с копированием, перечисления и т.д.).В подавляющем большинстве языков дефолт - это передача по значению. Передавать все по ссылке а значения передавать специальным синтаксисом - это разрыв мозга.

Метапрограммирование

Это ад. Всё, кроме этого — вкусовщина и вопрос привычки, но если в языке в 2025 году заявлено метапрограммирование — оно не может быть настолько убогим.

Тут вообще не поспорить.

комментарии — это ад; 2025 год на дворе, но я не могу просто прочитать документацию перед функцией, нет — передо мной выстроен частокол //!. Ну как так-то?

Пока Вы не написали, если честно даже не замечал. А сейчас сходил посмотрел, точно.

какие тесты я кладу рядом с кодом, какие в папку test?

Рядом с кодом - модульные. И получаете доступ ко всем приватным функциям модуля, что очень полезно. В различные test интеграционные и прочие тесты.

матчи внутри одной функции вместо нескольких голов, компилируемых во внутренний матч — ну чуваки, вы вообще хоть одну завалящую статейку про «как оно сделано у соседей» читали, или сразу свой велосипед строить начали?

Ну справедливости ради, такой велосипед не только в Rust но и в Python, Kotlin, C#, Scala, Swift и будет в Java. Думаю подход выбран для единообразия синтаксиса. В Rust нет перегрузок функций и делать исключения в виде особого синтаксиса именно для PM видимо было нецелесообразно. По карайне мере мне так кажеться.

"много голов" выглядит странно, как partial class

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

Тестировать приватные функции, сиречь — дентали реализации, — самый страшный грех разработчика (после плохих имен и некорректной инвалидации кэша).

Тестировать приватные функции, сиречь — дентали реализации, — самый страшный грех разработчика (после плохих имен и некорректной инвалидации кэша).

Вынужден с Вам не сгогласится. Звучит красиво и я сам раньше в это верил, но реальность такова что часто мы встречаем код вполне понятный, вполне компактный но чертовский неудобный и сложный для тестирования, и вот на него нужно написать тесты. И тогда перед нами ставят четыре стула:

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

  • не заморачиваться с инкапсуляцией и сделать все функции публичными. Думаю комментировать не нужно.

  • писать супер сложные и адски неудобные тесты с проверкой все возможных комбинаций всех инвариантов этой слогики. Но для этого лучше уже подойдут интеграционные или более реалистичные тесты.

  • писать на тесты на части приватной логики. Да местами прибиваем гвоздями детали реализации, с другой стороны, unit тесты это и есть история про прибивание гвоздями реализации в том или ином виде. Зато можно написать компактный хороший и простой код с закрытой реализацией и удобным тестированием.

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

Лично пришел к выводу что последнее меньшее из зол.

Ну, раз вы (и целая группа товарищей из создателей языков) пришли к такому мнению, то мне и сказать даже нечего.

Хотя нет, есть что.

писать супер сложные и адски неудобные тесты с проверкой все возможных комбинаций всех инвариантов этой логики

Ещё можно открыть для себя property-based тесты, которым скоро тридцать лет стукнет, и делать всё по уму. А именно, не писать ни сложные, ни запутанные, ни адски неудобные тесты, но тестировать именно реализацию через интерфейсы, а не хрен знает что.

Поверьте, за тридцать лет карьеры я насмотрелся на довольно запутанный код. На десяти с лишним разных языках. От сохранёнок в Оракле в 1997, аспектов в джаве и хаскеля в 2002 — до руби, эрланга и эликсира в последнее время. И знаете что? — Только когда тесты проверяют публичный интерфейс я могу быть спокоен за код.

Тестировать приватные хелеперы наоборот хорошо. Вот понадобилась Вам для реализации функция, которая хекс символ в число превращает. Пишете функцию(с кучей битовой магии), а рядом с ней тест.

Понадобится изменить реализацию и функцию выкинуть — ну выкинем тест и делов то.

Встроенные тесты хороши для тестирования вещей, которые модно было бы вынести в библиотеку, но лень/нет смысла.

хекс символ в число

Я понимаю, что это синтетический пример, но он не годится. Этот перевод никаким боком не является деталью реализации. Я вынесу его в библиотеку, потому что я привык работать в экосистемах, где создание библиотеки — процесс на три минуты. А если это просто еще один маленький хелпер — я его добавлю в уже существующую библиотеку vsyakoe_govno  — и буду тестировать там, а не захламлять тесты основного приложения, увеличивая время CI на ровном месте.

Тестировать детали реализации — нельзя, и это должно быть запрещеног компилятором — что-то вроде гигиены, линтера, если хотите. Просто смиритесь с тем, что если вам зачем-то это нужно — вы что-то делаете неверно. И попытка протестировать приватную функцию — бесплатно подарит вам возможность решить архитектурную проблемку (которая может стать миной замедленного действия) на самой ранней стадии.

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

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

Почему вы считаете что интерфейс пользователя нужно тестировать, а интерфейс которым пользуетесь вы - нет?

В тесты вкладывают довольно широкий спектр смыслов, но, как по мне, если у вас есть какая-то логика, то тесты вполне уместны для проверки ее корректности - не важно является эта логика интерфейсом или деталью реализации

Перегрузка функций, aka специализация, есть, но ее до ума уже лет 15 довести не могут, времена жизни мешают. И выкинуть не могут, на нее что-то в компиляторе завязано.

А можно ссылочку? Я что-то пока недостаточно вкатился, сам найти не могу. Спасибо заранее.

Вот оно, в тряпочку замотано. В принципе, если не совать в него вж или прочие фичи типажей, коими раст очень гордится, то оно работает. Фундаментальная проблема в том, что типажи позволяют много большее, чем специализация в классическом виде может покрыть. Ну и это оопшный стиль разработки, который не особо любят.

Спасибище!

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

Всё остальное, к сожалению, просто придирки. Сравнения с эрлангом и эликсиром не особо к месту.

У раста есть свои проблемы. Тема прибитого сбоку асинка не раскрыта, тайп касты через as, negative impls не упомянуты. То что Copy реализован для примитивов, что иногда приводит к неожиданному результату, тоже не упомянуто.

В общем есть много что обсудить. Я понимаю, что эти проблемы всплывают только при длительном использовании языка, и только в каких-то конкретных случаях, но представленное в статье не является хорошим аргументом.

Тема прибитого сбоку асинка не раскрыта, тайп касты через as, negative impls не упомянуты. То что Copy реализован для примитивов, что иногда приводит к неожиданному результату, тоже не упомянуто.

А что с ними?

async/await в языке, но рантайма нет — выбираешь tokio/async-std/smol

А остальное что

он действительно сбивает с толку, очень перегружен, и неудобен для написания

Этих претензий у меня как раз нет.

представленное в статье не является хорошим аргументом

Представленное в тексте (статья — рецензируемая сущность) не является аргументом в принципе. Если бы вы умели читать заголовки, у вас и такой мысли бы не возникло.

Да те же проблемы в чае, которая уже статья

  1. Странная передача параметров в функции, осложненнная потерей владения /сущности

  2. Проблемы единственности владения приводящие к сюру с Arc/Rc/RefCell для некоторых структур данных

  3. Адские макросы

  4. Проблемный FFI (тс просто не дошёл еще)

А в остальном, прекрасная Маркиза.... (с)

Претензии к строковой интерполяции, матчам, доктестам - не поддержу. Узковато

Узковато

Это очень странный вариант реакции, если честно. На мой вкус ваш комментарий можно без потерь общности метафоризировать так: «У нас в доме нет крыши, поэтому мы и канализацию делать не будем». Да, но в нашем климате и на нашем этаже — существование крыши может вообще никогда ничего не изменить, а срать мы хотим каждый день.

Во-первых, поздравляю с успешным вкатыванием! Я бросил раньше, видя глобальные проблемы. Правда я начал уже "зная брод" - я ранее наткнулся и переводил статью Александреску касательно Раста.

Что касается "узковато" разверну - видя гораздо серьезные (номерные) недостатки, именно эти проблемы считаю малозначащими плюшками, без которых легко обойтись. "Ну может они очень уж удобны максимум для {%d} спецов на 1000", 1.5f

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

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

Я бросил раньше, видя глобальные проблемы. Правда я начал уже "зная брод".

Можете поделиться, в какой альтернативе нет глобальных проблем?

- я ранее наткнулся и переводил статью Александреску касательно Раста

Почитал. Целых две и обе невероятно объективные.

Первая - почему Rust, разработчики которого поставили цель сделать язык безопасным управлением памятью и высокой производительностью, вместо этого не сделали еще один Си с бантиком.

Вторая - "Чуждый синтаксис. Это раздражает людей, пришедших из языков семейства Algol’а". Ну это по мне стыд и позор такие претензии предъявлять, что-то на уровне, язык плохой потому что у него маскот - краб.

в какой альтернативе нет глобальных проблем?

Я (внимание! лично я! это частное мнение! не претендующее на объективность!) не вижу архитектурных проблем в эликсире.

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

А по-моему, любые претензии предъявлять нормально. Вопрос — как на них реагировать. Мне однажды в issues одной из моих библиотек написали, что им пришлось её форкнуть и поддерживать форк (в чем обвинялся я, конечно) из-за того (присядьте, если стоите) — что — название главного модуля слишком длинное и они никак не могут его запомнить. Я честно обдумал претензию и решил, что не стану переименовывать библиотеку, но если бы это было до публикации и первой сотни пользователей — переименовал бы, потому что вообще-то в чем-то они были правы.

Я (внимание! лично я! это частное мнение! не претендующее на объективность!) не вижу архитектурных проблем в эликсире.

Во первых, вопрос не про архитектурные проблемы(что бы это не значило) а скорее глобальные, а они есть у всех. В данном контексте - проблема управления памятью, которая не решена нигде.

Мы имеем либо:
- просто и безопасно но не быстро(GC языки)
- просто и быстро но не безопасно(C/C++, все остальные виды C с бантиками)
- быстро и безопасно но не просто(Rust)

Я не знаю сейчас более менее живых языков, которые решают одновременно три эти проблемы. В этом контексте я не понимаю претензии к Rust. Разработчики сделали ставку на скорость и безопасность решая конкретную проблему - сделать быстрый язык без отстрела себе ног, это имело свои последствия в виде сложности. За все нужно платить. Можно было лучше? Наверное. Но где те идеальные инструменты где все и быстро и надежно и просто? Очевидно нигде. Когда появиться такое то уже будет интересно сравнить или действительно прийти к выводу что Rust не торт. А сравнивать с самострелами в контексте простоты наглухо закрывая глаза на отстреленные конечности такое себе.

Это я в контексте прошлого комментария.

По поводу Эликсира, в данном контексте его проблемой была бы производительность.

я не понимаю претензии к Rust

Я знаю, что наличие претензий к священному идолищу похапешников и не осиливших С++ — расту — это смертный грех. И, тем не менее, я позволю себе вольность иметь претензии даже к господу Богу, если он начнет козлить.

Я не понимаю, почему бы мне, как я это делаю во всех остальных современных экосистемах, в которые хоть немного вовлечен, не высказать эти претензии и не обсудить их без бессмысленной казуистики «а где лучше?». Если все соседи срут в подъезде — это не значит, что подъезд не нужно чистить, а главное — не даёт мне индульгенцию тоже срать в этом подъезде. Мне так кажется.

Да это не на Ваш комментарий ответ а на то что выше, а ответил Вам скорей в контексте эликсира. Специально написал "Это я в контексте прошлого комментария." Признаю, сформулировать получилось неудачно.

Я не поклоняюсь Rust, для меня это не более чем отвертка для решения ряда задач, как и любой другой ЯП.

Я не понимаю претензий вида Rust не совершенен - я в нем разочаровался, и это еще на фоне таких "совершенных" языков как C++.
Есть один инструмент который одной делает хорошо а другое - плохо и другой который имеет обратные характеристики. Мы все ждем идеальный инструментов, но что-то пока не завезли. Когда завезут то что во всем будет лучше и решать большинство проблем, мне вот честно нафиг тогда не нужно будет копятся во временах жизни. Но это когда-то и возможно. А пока что есть то есть.

Теперь пару слов про Cure. Как я понимаю, автор говорит о нём, когда упоминает что написал свой язык.

Бесконечный респект за написание своего языка, но правильно ли я помню что пару месяцев назад сайт был полностью сгенерирован ЛЛМами, и на котором были заявления, не подтверждающиеся реально существующим кодом? Если кому-то очень скучно, можно одним глазом глянуть тред на хакерньюсе и другим на вебархив.

правильно ли я помню что пару месяцев назад сайт был полностью сгенерирован ЛЛМами

Да. Сайт до сих пор полностью сгенерирован ллмками. Я не вебмастер, пардон. Документация — на 95%.

Что именно не подтверждается реально существующим кодом — ни подтвердить, ни опровергнуть не могу. Я уже использовал компилятор в продакшене.

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

Язык доступен в репозитории, у него есть тесты и примеры. О готовности к продакшену я вообще ничего не заявлял. О новых возможностях и багфиксах сообщаю на посвященном языку форуме https://erlangforums.com/c/beam-language-forums/cure-forum/131

"{} + {} = {}", a, b, c?

Чисто для протокола, это не строковая интерполяция, это банальный Printf.printf с выводом типов.

Да и вообще, паттерн-матчинг после эрланговского выглядит убогим

Это естественно, поскольку язык статически типизирован + на pattern matching накладывают разные неявные условия вроде тотальности, скорости и т.д.

Вообще в Русте pattern-matching, насколько я понимаю, взят из OCaml, где базовой конструкцией является match with (есть ещё function, имитирующий поведение Miranda/Haskell, но это костыль — могу обосновать, если потребуется). Кроме того, язык Руст сделан для С++ников, а это означает зубодробительный синтаксис (я помню, как в конце 90х мне лично нравился подъязык шаблонов с <>, кои являют какой-то дикий идиотизм, кмк). Ну так там у С++ников заведено — они в глубине души хотят, чтобы текст выглядел сложно, понятно, в общем, за что деньги плотют. :-)

P.S.
Мне как раз Руст не нравится своей тяжеловесностью и непонятной областью применения. Да, теор. функциональщики на него откровенно дрочат, но им он нужен «чтобы слоники побегали», а для индустрии это усложнение ради усложнения (хотя, зато job security).

Сейчас для ЯВУ везде достаточно памяти => стоит брать языки со сборкой мусора. А если нужен низкий уровень, то язык должен быть близок к железу, а значит не слишком экспрессивный. Иначе получится, что слишком многое зависит от поведения компилятора (см статью C Is Not a Low-level Language Your computer is not a fast PDP-11. за авторством Дэвида Чиснола).

для ЯВУ везде достаточно памяти => стоит брать языки со сборкой мусора

И получить просаживание 95% перцентиля в разы

Либо пруф, либо идите мешки ворочать, пожалуйста.

Достаточно серьёзное исследование?

https://arxiv.org/html/2405.11182v1

Конечно, я взял случай когда запросы лёгкие и/или нагрузка велика

За ссылку — прям нечеловеческое спасибо, я как раз думал, что еще принести в joerl и SMR — совсем вылетели из памяти. Супер, то, что надо.

Теперь по тексту. Это сравнение апельсинов с яблоками. Я не смотрел, как сделан GC в го, но у меня нет причин предполагать, что как-то значительно лучше, чем весь остальной язык. Нельзя взять два разных компилятора, написать два разных кода, потом построить графики memory reductions и vCPU utilization и сделать из этого хоть сколько-нибудь релевантные выводы, вам об этом скажет любой, кто сидел, глядя на графики реального времени в эрланговском обсервере. Там бывают очень значительные скачки в самых непредсказуемых местах.

Главка «Confirming the GC Overhead» не случайно закопана в середину: в ней показано, что наличие сборщика мусора приносит оверхэд (сенсация!). Причем не любого, но «when the full GC is triggered» — в эрланге full GC не вызывается никогда, например, если специально не попросить.

Еще в тексте по ссылке нет ни единого упоминания о «просаживании 95% перцентиля в разы».

SMR — совсем вылетели из памяти. Супер, то, что надо.

Я так и подумал

по ссылке нет ни единого упоминания о «просаживании 95% перцентиля в разы».

Latency P99 Java/C++ в разы
Latency P99 Java/C++ в разы

И другие картинки latency

Главка «Confirming the GC Overhead» не случайно закопана в середину: в ней показано, что наличие сборщика мусора приносит оверхэд (сенсация!).

Это, кстати, необязательно. Скажем системная сборка мусора может быть много быстрее вызовов delete в С++, в чём можно легко убедиться, написав программу, выделяющую груду всякой фигни и сравнив ситуацию, когда вы высвобождаете память, и когда нет. :-)

В эрланге у колбеков gen_server есть параметр возврата «:hibernate», который косвенно управляет сборщиком мусора. Я прям воодушевился, когда об этом узнал. Провел трое суток в запое^W в игрищах с ним на бенчмарках и узнал, что пока процесс не таскает в стейте гигабайт какой-нибудь гадости — лучше его не трогать вовсе.

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

Ох, далеки вы от народа... У нас тут на Питоне пишут — это просаживание в 10 раз всех перцентилей. И ничего.

Все зависит от конкретных требований, конечно, но сборка мусора это явно не панацея, и во многих ситуациях абсолютно вредна. Почитайте про sqylla и cassandra. Ее портировали на c++ не забавы ради

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

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

Мне раст нравится тем, что он не прощает плохие архитектурные решения, и о большинстве ошибок сообщает еще на этапе компилирования, без этих скрытых ошибок в рантайме, которые трудно потом отловить

Я недавно начал работать над реализацией реалтайм системы и оказалось что перекрестное владение данными это очень плохая штука, и сейчас, подумав над архитектурой, оказалось что я просто делал все неправильно, тот же джаваскрипт мне позволил бы это реализовать как мне угодно, и получился бы очередной уродец, из за которого общее восприятие приложения превратилось бы в разочарование от вечных провисаний

Почитайте про sqylla и cassandra.

Вы сколько за свою жизни написали баз данных? Я что-то не видел на заглавной странице большого баннера: «только для написания высоконагруженных, высококонкурентных приложений».

Сборка мусора вызывает постоянные непредсказуемые остановки рантайма […]

В общем случае это не так.

тезис о достаточности памяти приводит к тому […]

Ножи приводят к убийствам. Не нужно экстраполировать примеры до нерелевантных.

над реализацией реалтайм системы ....тот же джаваскрипт 

ну после JS, в этой области, конечно почти любой ЯП выигрышнее выглядит.

еще бы с питоном сравнить =)

сборка мусора это явно не панацея, и во многих ситуациях абсолютно вредна. Почитайте про sqylla и cassandra

Сборка мусора как таковая это небольшая доля разницы между сцилой и кассандрой) ее можно было бы так же «портировать» с джавы на джаву и получить незначительно отличающийся результат (другое дело что зачем если можно выжать еще 10% перфоманса, «портировав» на плюсы)

Там где нужно гарантировать лейтенси в микросекунды сборка мусора действительно так себе затея, но к субд таких требований обычно не ставят)

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

Не многих, а некоторых. Вы знаете, что вот если вы делаете быстрый интерпретатор, то если его писать на ассемблере, то получится скорость недостижимая для компилятора? Там есть некоторая хитрость в основном цикле. И что, теперь нам на ЯВУ не писать? :-)

Ну вот я же не забавы ради начал OTP портировать.

Допишу прозрачную кластеризацию и сделаю то, ради чего весь сыр-бор: прогоню нагрузочные тесты на расте vs erlang. Там и посмотрим.

это не строковая интерполяция, это банальный Printf.printf с выводом типов

Ох. Это строковая интерполяция, у которой под капотом сегодня принтф с выводом типов, а завтра может быть ллм. Детали реализации — вопрос сто тридцать пятый. Интерфейс этой штуки называется «строковая интерполяция».

взят из OCaml

Да какая разница, кто откуда взят-то. Эликсир доктесты взял из питоновской библиотеки, и посмотрите, где сейчас та библиотека, и где доктесты в эликсире.

Конструкция if let Some(some) = some {} — это даже чисто семантически — кошмар, особенно в строго-типизированном языке. У меня нет никаких проблем с match per se, более того, я пошел по именно этому пути в Cure, но лично я зверею, когда мне приходится потратить чуть ли не полчаса, чтобы досконально разобраться, как именно матч работает.

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

Насколько я понимаю, там разница как раз не в реализации, а в интерфейсе. В строковой интерполяции вот у вас строка, в ней всё. В классическом printf информация разнесена — есть размеченная строка и есть аргументы функции.

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

let a = 1 and b = 1.0 in
[%string "Checking if %d$a == %f$b. Result %b$(a = int_of_float b)!"]

и

let a = 1 and b = 1.0 in
 Printf.sprintf "Checking if %d == %f. Result %b!"
  a b (a = int_of_float b)

В верхнем случае по самой строке понятно, что куда подставляется, а вот во втором случае надо читать третью строку. Это не проблема, если тут одна-две переменные, а вот когда вы генерируете шаблон, вызывая Printf.*printf c пятью и более аргументов, тут уж несложно и перепутать что-нибудь.

Если мы вернёмся к примеру "{} + {} = {}", a, b, c — в нём строки явно не хватает, чтобы понять, что получится.

Кстати, пример со строковой интерполяцией вверху показывает, чем плохи выражения в строке — сильно мешают понять, каков будет окончательный вид. Согласитесь, вариант с sprintf в данном случае читаемее.

Не соглашусь. Мне кажется, не нужно держать разработчика за дебила. Дайте мне разные инструменты, я стану забивать гвозди одним, а заколачивать шурупы — другим. Не нужно за меня решать, дать мне только утюг, или только микроскоп.

Что мне помешает (кроме деменции) написать в данном случае так:

let a = 1 and b = 1.0 and c = (a = int_of_float b) in
[%string "Checking if %d$a == %f$b. Result %b$(c)!"]

Ничто. Более того, так и пишут коллеги. Как известно, забивать гвозди микроскопом удобнее, чем утюгом — шнур не мешается и хват лучше. Да и вообще, когда в руках микроскоп, любая проблема начинает походить на гвоздь. А взяв паяльник в руки, каждый невольно становится немного криптоаналитиком. :-)

Тем не менее, основная идея в том, что строковая интерполяция лучше подходит к чуть более крупным текстам. То есть, если подбирать молоток по руке, по мере роста размера текста/количества переменных идут сначала просто string_of_int/конкатинация, потом printf, затем уже строковая интерполяция. Разумеется, с нахлёстом.

И вставка выражений в интерполируемую строку оправдано, когда эта строка многострочна и занимает, скажем, абзац. Тогда let c вверху потребует перевести взор, а это — то, против чего и борется строковая интерполяция.

Ну да. Более того, я вообще-то могу собирать строчку в отрыве от её вывода, вот как я собираю документацию модуля в эликсире: https://github.com/am-kantox/finitomata/blob/v0.34.0/lib/finitomata.ex#L189-L193 (копипаста кода, чтобы не скакать по ссылкам — ниже).

  doc_main = "…"
  doc_options = """
  ## Options to `use Finitomata`

  #{NimbleOptions.docs(@using_schema)}
  """
  doc_appendix = "…"
  doc_readme = "README.md"
               |> File.read!()
               |> String.split("\n---")
               |> Enum.at(1)

  @moduledoc Enum.join(
    [doc_readme, doc_main, doc_options, doc_appendix], "\n\n")

Как раз передача по умолчанию по перемещению это наиболее лучший вариант.

Передача по ссылке предполагает, что писатель должен думать когда нужно перемещать, а когда нет и не забыть последнее использование переместить для эфективности.

Можно посмотреть как с этим дела обстоят в C++ и убедиться, что это неудобно и создаёт ошибки при использовании после перемещения.

struct A {}

fn x(a : A) {}

fn main() {
    let a = A {};
    
    x(a);
    x(a);
}

error[E0382]: use of moved value: a
--> src/main.rs:11:7
|
8 | let a = A {};
| - move occurs because a has type A, which does not implement the Copy trait
9 |
10 | x(a);
| - value moved here
11 | x(a);
| ^ value used here after move

А когда мы хотим ссылку, то есть заимствевоние то пишем об этом явно

struct A {}

fn x(a : &A) {}

fn main() {
    let a = A {};
    
    x(&a);
    x(&a);
}


Внезапным исключением являются "методы", где компилятор за нас умеет ставить заимствевование

https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=a0919eb2079c6a9c2b5508998159eebf

trait IA {
    fn x(self: &mut Self);
    fn y(self: &Self);
}

struct A {}

impl IA for A {
    fn x(self: &mut Self) {}
    fn y(self: &Self) {}
}

fn main() {
    let mut a = A {};
    
    a.x();
    a.x();
    a.y();
    a.y();
}


Ну и конечно если мы перемещаем, то не соберёться

trait IA {
    fn x(self: Self);
}

struct A {}

impl IA for A {
    fn x(self: Self) {}
}

fn main() {
    let a = A {};
    
    a.x();
    a.x();
}

error[E0382]: use of moved value: a
--> src/main.rs:15:5
|
12 | let a = A {};
| - move occurs because a has type A, which does not implement the Copy trait
13 |
14 | a.x();
| --- a moved due to this method call
15 | a.x();
| ^ value used here after move



А про макросы всё верно.

Видимо не посмотрели как сделано в других языках и вышло что вышло.

Обещают поправить в будущем.
Надеюсь сделают как полагается с нормальной типизацией и гигиеной.

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

Если еще указывать недостатки Rust, то явно стоит упомянуть ограничения константных выражений. Например, нельзя как в Typescript написать внутри модуля:

const params = {key: 123, key2: "133"}

Ни в JavaScript, ни в TypeScript константных выражений не существует, ключевое слово const лишь запрещает заменять ссылку, на которую указывает переменная, и не более того

Речь не о том, что в Typescript нет константных выражений, а о том, что в расте их нельзя задать таким образом. Это удобный способ для хранения настроек например.

Тогда получается, что это на самом деле не недостаток, а лишь ваша личная вкусовщина, потому что пихать настройки в константы абсолютно ничего не мешает

mod params {
    pub const KEY: i32 = 123;
    pub const KEY2: &str = "133";
}

Словарь в коде вы принципиально не хотите видеть?

Чем принципиально отличается ваш словарь от словаря на константах в модуле? (Подсказка: правильный ответ — ничем.)

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

Если вам надо перечислять все ключи, — это не словарь. Это во-первых. Во-вторых, если вам надо — экспортируйте эксплицитный список, или функцию get. Если вы обходите словари каждый день — напишите макрос (которого нет в стандарте ровно потому, что это антипаттерн).

Пруф, что это антипаттерн в студию.

Добейтесь от https://m-w.com списка всех знакомых ему слов, для начала.

Аргументный аргумент. Метод keys() в растовском HashMap почему-то присутствует, хотя это в вашем понимании антипаттерн. И в документации приведен обходной путь как засунуть мапу в константы. Видимо очень популярный антипаттерн, да?

А какую сверхважную задачу решает словарь в константе, что он там так жизненно необходимо нужен?

Например у JS или Питон кодеров - это стандартный подход, объявить на самом высоком уровне константы, стейт. Они видят, что это удобно и понятно, но по неясным причинам этот подход в Расте не работает. В константах даже строку нельзя собрать, типа const APP = APP_NAME + APP_VERSION. По факту разработчики сделали столько ограничений на константы, что весь сопутствующий функционал сделан на макросах, либо не сделан вообще и от привычных вещей приходится отказываться.

Потому что в Расте нет констант в привычном понимании, тут это данные времени компиляции. А CTFE ещё толком не завезли.

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

А я не говорил про константный стейт. Сможете просто создать и инициализировать стейт для модуля без использования функций? Так, чтобы это была одна структура типа State.

без использования функций

Опять какие-то странные вкусовщины?

Так, чтобы это была одна структура типа State.

Это неразумно, потому что неограниченный доступ на редактирование структуры является небезопасным, и обязательно должна существовать ещё одна структура, которая будет контролировать получение доступа на редактирование

Глобальные стейты используются, наверно, в каждой второй программе на Rust, и всем норм, но вы зачем-то предъявляете к ним какие-то бессмысленные требования

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

Как конкретно обойтись, не потеряв безопасность?

Во-первых, понимание безопасности к всех разное. Во-вторых, если бы я знал точный рецепт, то пулл реквест уже лежал бы в репозитории раста.

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

Думаю, если бы точный рецепт существовал, то пуллреквест лежал бы в репозитории раста ещё лет десять назад, раз не лежит — значит скорее всего на то есть причины

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

Если честно, я внимательно перечитал ветку дважды, и так и не смог понять, кому и зачем это может быть нужно. Так что рецепт-то, может, и есть, но…

Я думаю, что я знаю, как это можно реализовать, но я миссионерствую в сторону облегчения вкатывание эрлангистам; если вы хотите помочь джаваскриптизёрам — ну сделайте крейт, делов-то. Макрос это стопроцентно разрулит.

Унифицирует компилтайм- и рантайм-алгоритмы. Если у меня есть компилтайм-словарь, то я по нему могу построить в компилтайме, скажем, обратный словарь, причём ровно той же функцией, которой я бы сделал это в рантайме, со всеми вытекающими (вплоть до prop-based-тестирования этой функции поддерживающей только рантайм библиотекой). Но это если язык достаточно выразителен, конечно.

это если язык достаточно выразителен, конечно

Вроде достаточно, чтобы компилятор умел исполнять уже скомпилированный код.

Нет, конечно, потому что результат этого исполнения нужно ещё как-то проинтерпретировать (в смысле, компилятору ещё нужно понять, что там в него выплюнули, и что с этим дальше делать).

В языке, в котором всё — expression, думать не надо, надо вставлять выплюнутый expression по месту.

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

Эликсир именно так с макросами и работает (только экспрешены выдаются не кодом, а AST).

Когда компилятор видит макрос, он конвертирует параметры в AST, и передаёт (с гигиеной, конечно) в тело макроса, который технически тоже код на эликсире, который может, например, вообще напрямую корёжить этот AST, или пользоваться хелперами верхнего уровня. На выход макрос должен вернуть валидный AST, который компилятор заинжектит по месту. Когда разворачивать больше нечего — он начнет «компилировать».

Если напрямую работать с AST — получается немного экстравагантно. А если хелперами, то так:

defmacro lookup(id \\ @id, match) do
  name = with ast when not is_binary(ast) <- match, do: Macro.to_string(ast)
  quote generated: true, location: :keep, do:
    {unquote(name), Antenna.whereis(unquote(id), unquote(name))}
end

unquote/1 через гигиену достанет значение из контекста, а quote/2 — превратит код в AST, который будет вставлен по месту.

Когда компилятор видит макрос, он конвертирует параметры в AST

Всё, сдулась унификация.

В терминах плюсов я говорю о constexpr/consteval, вы говорите о рефлексии из 26-го стандарта. Думаю, нам обоим очевидно, что есть задачи, решаемые вторым, но не первым, но первое удобнее.

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

К сожалению top level паттерн матчинг ни один язык кроме эрланга не умеет.

Не понял. А ML-семейство вроде хаскеля, идриса, агды и так далее?

Ваши микроскопы плохо забивают наши гвозди; тут в полях людям до сих пор приходится парсить странные бинарные протоколы, типа «первые три бита — тип, еще два бита — флаг, потом тридцать один бит — длина, потом — строка этой длины.

<<type::3, flag::2, len::31, text::binary-size(len)>> = input

Это вообще ортогонально обсуждаемому вопросу.

Но идея забавная, надо подумать, впиливается ли это достаточно легко в одну мою либку.

Почему ортогональная? = — «оператор» матча в эрланге и эликсире. Слева — молот паттерн, справа — серп матч. Так же точно работают все остальные матчи, типа

%{foo: [head | _tail]} = %{foo: [1, 2, 3]}
head #⇒ 1
1 = head # всё отлично, матчится
2 = head # MatchError raised

<<bit::2, _::6>> = "a"
bit #⇒ 1
Integer.to_string(?a, 2)
#⇒ "01" <> "100001"

case "a" do
  <<1::2,_::6>> -> :ok
end
#⇒ :ok

Почему ортогональная?

Потому что вопрос о том, определяются ли функции через паттерн-матчинг по аргументам напрямую, насколько выразительная сила этого паттерн-матчинга совпадает с обычным case, и какая вообще эта выразительная сила — это примерно два с половиной разных вопроса.

К сожалению top level паттерн матчинг ни один язык кроме эрланга не умеет.

Haskell, Miranda, Clean, OCaml. Это просто чуть более свежий синтаксис, хотя, если рассматривать Prolog и Refal, то ещё не известно, кто на ком стоял.

match with — это OCaml'овская конструкция. В других языках обычно используются ключевые слова case of.

Паттерн-матчинг

Мультиметоды нарушат статический диспатчинг, там и без них неразбериха с dyn/impl.

Ссылки

Мне нравится что явно отличается move и ref. Иначе, изменение сигнатуры изменит поведение в месте вызова. Btw, до 2013 в расте были зеленые треды и сборка мусора, никаких умных указателей xD

метапрограммирование накостылено сбоку (что является, конечно, следствием отсутствия прямого доступа к AST)
Почему нельзя было сделать по-человечески? Что с unquote — тоже неясно

Свечку не ждержал, но явно что-то не так пошло.
Изначальная идея у них была здравая, взять syntax-rules из языка Scheme и адаптировать под себя. Там есть гигиена, шаблоны и репорт ошибок. Делал библиотеку макросов bitstring, которая реализует Erlang Bit Syntax, возможности там космические. Когда пытался портировать на Rust, ничего не получилось.

Вторая опция - процедурные макросы, должно было быть похоже на Defmacro/syntax-case с трансформацией из кода во время сборки. Их выбрали как основной способ написания макросов сложных. Раньше ими невозможно было пользоваться, время сборки на 10 умножалось.

Тесты, Документация

Приемлемая реализация, в 2014 это выглядело очень круто, сразу проект с тестами идет и документацией. Ну и cargo, пакеты из коробки, а самое главное интегрируются без боли. Это можно и к минусам отнести, как в Питоне, только один способ - делать как у всех.

Филосовия языка - надежность и скорость(не разработки xD). Никогда разработчики не утвеждали, что нашли серебрянную пулю, так говорят фанатики и у людей завышенные ожидания. Сейчас из Rust модно переходить на Zig.

Мультиметоды нарушат статический диспатчинг, там и без них неразбериха с dyn/impl.

Нет, конечно. Компилятор превращает мультиметоды в match под капотом даже в эрланге.

Приемлемая реализация [тестов, документации]

На троечку, о чем я и написал. «проект с тестами идет и документацией» сейчас можно везде, но если я не могу штатными средствами добавить в документацию страничку текста — это позор. Пакетный менеджер тоже уже давно не новость.

Сейчас из Rust модно переходить на Zig.

Вопрос не в моде. Мне — где-то в комментариях к нерелеватному тексту — про что-то важное — сказали, что в расте оно появилось, и я, наконец-то решил попробовать покрутить сам. И раст внезапно оказался довольно внятным. Мне хватило 8 часов с нуля, чтобы написать довольно работоспособную библиотеку, реализующую акторную модель (см. ссылку в начале текста). Ну не прям «реализующую», конечно, но благодаря комментариям умных людей, — приближающуюся близко. На плюсах у меня бы ушел на это год.

К сведению пакетный менеджер раста не умеет ничего, кроме пакетов раста. Хотите подсистему с библиотекой на С, например - сами, все сами е-сь.

И мееееееедленный.

кроме пакетов раста

Конкретно в этом не вижу проблемы. Я бы обернул библиотеку на С в раст-пакет, если бы мне потребовалось. Это тривиально (поправьте, если ошибаюсь).

мееееееедленный

Я не проверял, но у меня есть гипотеза, близкая к уверенности, что это как раз потому, что он написан правильно и умеет резолвить ситуацию «обнови весь мой зоопарк до самых последних версий». Там граф, и я участвовал в его имплементации для эликсира — задача не из простых.

Быстрые пакетные менеджеры обычно не умеют правильно разрешать кросс-зависимости до самых последних версий.

Я бы обернул библиотеку на С в раст-пакет

Это то понятно, обязательно, т.к. Раст бесшовно интегрироваться не может.

А кто будут пересобирать зависимости внутри С-кода при необходимости?

Короче, тривиальный make в этом оказывается мощнее.

что это как раз потому, что он написан правильно и умеет резолвить ситуацию

нет. потому что раст библиотеки, возможно что из-за долгой компиляции, бьются на мелкие кусочки. Там где тянется 30 с-зависмостей, у раста может быть и 200. Я в это ткнулся в openssl

тривиальный make в этом оказывается мощнее

Э-э-э… Напоминаю про граф зависимостей, который надо разрешить. Это сложнее, чем руками пересобрать крейт, не? Я же могу при сборке крейта вызвать команду из шелла?

Э-э-э… Напоминаю про граф зависимостей, который надо разрешить. Это сложнее, чем руками пересобрать крейт, не? Я же могу при сборке крейта вызвать команду из шелла?

Тут, если вы присядите на 5 минут, зарыта конкретная задача:

Make, как известно, никому не нравится, поэтому все понаделали своих велосипедов с говносинтаксисом (см dune, CMake :-)). Почем?

Потому, что нет универсального описания динамических зависимостей make. А можно ли это сделать? То есть, задача заключается в том, что компилятор или другой обработчик некоторых файлов указанных в Makefile должен сообщать, от чего зависит конкретный файл. Вот эта область частично проработана, но не до конца.

Ну да. И (эта информация проверена) пакетный менеджер эликсира это умеет, и (на это я слепо надеюсь) пакетный менеджер раста это умеет.

А make из коробки — не умеет. Но он, наверняка, умеет вызвать стороннюю утилиту и распарсить её вывод. Поэтому я не вижу причин, по которым make не смог бы, например, научиться понимать формат любого пакетного менеджера линукса и просто делегировать разрешение зависимостей туда.

И как ваше архитектурное чутьё не видит тут Задачу?

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

А вы ведь должны видеть! :-) Давайте поиграем:

Скрытый текст

С моей точки зрения это — отсутствие единого для разных экосистем протокола и единого языка описания этих зависимостей.

А что скажете вы? Какие причины?

Люди в мире IT настолько твердолобы, что им требуется либо генерализованное решение, которое понимает зависимости в пакетных менеджерах brainfuck и COBOL, либо у них лапки, и гори оно огнём.

Edit: почти угадал :)

Кмк, эта проблема на данном уровне не решается, и слава богу. Оно требует совместных действий разных людей, а это приведёт к уродцу вроде CMake.

Общее решение, кмк, не существует, поэтому нужно сделать удобные «чёрные ходы». В общем, это сложная задача на данный момент.

Но сейчас ведь экосистема состоит из этих «чёрных ходов»! Попробуйте собрать многоязыковой проект...

Да нормально всё собирается, make просто делегирует полномочия сборщикам этих языков.

Версии я беру (как уже говорил выше) — из apt для моей таргет-убунты. Да, приходится поддерживать deb файлы для всех разноязыких частей, но это буквально тривиально.

но если я не могу штатными средствами добавить в документацию страничку текста — это позор.

И что по-вашему, "штатные средства"? Почему #![doc = include_str!("path/to/file.md")] вы не считаете таковым?

Как надо: да как угодно, но только, чтобы в текст комментария не вторгались управляющие символы, не имеющие к нему никакого отношения.

Ну пишите комментарии в виде /** ... */ (вместо ///) или /*! ... */ (вместо //!).

И чтобы блоки кода из документационных файлов, вставленных директивой #![doc = include_str!("path/to/file.md")], не пытались превратиться в доктесты и не ломали сборку (какие ещё доктесты в отвлеченных текстах, алё).

А если мне нужно, чтобы они в доктесты превращались? Удобная практика -- внедряете Readme.md проекта на github-е с примерами кода и они проверяются при сборке. Если вам не нужна проверка, то напишите ignore или no_run.

▸ матчи внутри одной функции вместо нескольких голов, компилируемых во внутренний матч

Ужас. Во-первых, внутреннее устройство торчит наружу. Во-вторых, куда цеплять документацию? В-третьих, разнесут по нескольким файлам (да даже просто в файле по куче мест) и ищи-свищи, где весь код. В-четвертых, теперь IDE вам весь код handle_event не свернет, чтобы глаза не мозолил, только отдельные ветки

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

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

Собственно, почему в документации предпочитают строчные комментарии вместо многострочных, думаю объясняется вот этим примечанием из ссылки про синтаксис комментариев, что я дал выше:

It is conventional for doc comments to contain Markdown, as expected by rustdoc. However, the comment syntax does not respect any internal Markdown. /** `glob = "*/*.rs";` */ terminates the comment at the first */, and the remaining code would cause a syntax error. This slightly limits the content of block doc comments compared to line doc comments.

▸ матчи внутри одной функции вместо нескольких голов, компилируемых во внутренний матч

Еще один серьезнейший недостаток такой реализации, не позволяющий рассматривать его хоть сколь-либо серьезно -- невозможность добавить код до и после матча в ту же функцию. Не представляю, кому он был бы нужен в таком виде.

Не представляю, кому он был бы нужен в таком виде.

Я уже составил себе впечатление о вашем кругозоре, можете расслабиться и не усугублять.

На хабре не одобряют личностей, вылезших почесать свое ЧСВ. То, что вы сделали работу, 80% которой любая LLM сегодня сделает, еще ни о чем не говорит. Хотели отзывов -- умейте их принимать.

Абсолютно насрать, что любят, или не любят, хабрахомяки.

«Не представляю […]» про то, что существует десятилетиями и успешно используется — это не отзыв, это признание в собственной некомпетентности.

80% которой любая LLM сегодня сделает

Зато я, в отличие от вас, делаю, а не пержу в лужу.

Компилятор превращает мультиметоды в match под капотом даже в эрланге.

Имя однозначно идентифицирует функцию и не создает неопределенностей для передачи в ffi и инференса. Итак тяжело лайфтаймы расставить правильно, а если добавить перегрузку, вообще труба.

На плюсах у меня бы ушел на это год.

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

Мне хватило 8 часов с нуля, чтобы написать довольно работоспособную библиотеку

Компилятор хорошо отполировали и примеров много, раньше новичков borrow checker булил. Есть места, где не влазит задача в идеоматичный rust, но их крайне мало и самое стремное в готовые крэйты завернули. Если не делать zero-copy/state-of-art, то особо нет проблем с проектированием.

Имя однозначно идентифицирует функцию и не создает неопределенностей для передачи в ffi и инференса. Итак тяжело лайфтаймы расставить правильно, а если добавить перегрузку, вообще труба.

Простите, мне не понятен этот аргумент. Компилятор (даже парсер, ну) первым проходом собирает разные головы и упаковывает их в матч с одной головой. Дальше по накатанной. Повторюсь, компилятор эрланга делает именно так: в коде, который видит «главный» компилятор — одна функция с матчем внутри.

Обладатель черного пояса по ООП напишет за неделю.

И оставшиеся 358 дней будет разбираться с сегфолтами, UB и так далее.

На плюсах у меня бы ушел на это год

Скорее пара часов, чтобы найти уже готовый велосипед, даже с выбором цвета педалек.

Вот, на Хабре собутыльник сокамерник автор по форуму свою пишет лет 10. (Произносим заклинание вызова @eao197)

Угу, и окажется, что его (как actix в расте) — писал человек, представления не имеющий о том, как выглядит адекватная реализация акторной модели.

как выглядит адекватная реализация акторной модели

И как же она выглядит?

В первой строке этого текста есть ссылка на текст о моей реализации, там всё расписано.

Могу предположить, что речь идет вот об этом абзаце:

Акторная модель притворяется краденой из эрланга, с примитивами GenServer и GenStatem, с деревьями супервизоров, с боксированными сообщениями, мэйлбоксами, и привычной терминологией

При этом у вас есть еще и статья Акторная модель для дошкольников, в которой еще сколько-то текста написано.

Можно попросить ткнуть пальцем в конкретное место (места)?

Про то, как реализована акторная модель в эрланге, — лучше читать на сайте эрланга. Там нет «конкретного места», это всё равно, что попросить ткнуть пальцем в «конкретные места» изложения десятиклассника по «Братьям Карамазовым», чтобы оценить роман дяди Фёдора.

P. S. У меня нет никаких статей. У меня есть тексты на среднего качества форуме в интернете. Статья — это рецензируемая сущность.

Про то, как реализована акторная модель в эрланге, — лучше читать на сайте эрланга

Предсказуемый ответ. Когда у человека с Erlang-ом головного мозга в руках оказывается молоток, то все остальное начинает выглядеть как гвозди.

Насколько я помню, акторная модель она про очень простые правила поведения акторов как независимых и изолированных друг от друга сущностей. А именно:

  • акторы реагируют на входящие сообщения;

  • получив сообщение актор реагирует на него исходя из своего текущего состояния и: a) может изменить свое состояние; b) может отослать конечное количество сообщений другим акторам (включая и самого себя); c) может создать конечное количество новых акторов.

Как бы на этом все.

В Erlang-е на это навесили кучу всякого разного (типа супервизоров и разные штуковины из OTP). Только вот почему это следует относить к модели акторов -- хз.

Кстати говоря, я не помню, чтобы Джо Армстронг описывая историю создания Erlang-а говорил о том, что они в Erlang-е реализовывали именно модель акторов.

Предсказуемый ответ. 

Да ну? А какой был бы правильный ответ на вопрос «распишите поподробнее, что означает ваша фраза „акторная модель притворяется краденой из эрланга“»?

почему это следует относить к модели акторов — хз

Вы бы научились читать и понимать написанное — это относится к вашим собственным вопросам. Между «правильная модель акторов» и «правильная реализация модели акторов» — не пропасть даже, а целый метауровень (в смысле MOF).

я не помню, чтобы Джо Армстронг

И что из вашей амнезии должно воспоследовать?

Если честно, меня диванные аналитики с обтекаемыми формулировками и пустыми гитхабами преизрядно утомили. Идите в свою песочницу, а?

А какой был бы правильный ответ на вопрос

У меня не было такого вопроса.

Вы бы научились читать и понимать написанное

Тогда просветите меня как мне стоит понимать фразу:

представления не имеющий о том, как выглядит адекватная реализация акторной модели

высказанную вами в мой адрес.

И что из вашей амнезии должно воспоследовать?

Дело не в моей амнезии. Есть известный текст от Джо Армстронга от 2007-го года: A History of Erlang, где история его работы над Erlang-ом расписана весьма подробно. Может вы сможете привести цитату оттуда, которая бы говорила об использовании модели акторов при разработке Erlang-а?

просветите меня как мне стоит понимать фразу

А почему меня это должно волновать? Как хотите — так и понимайте, мне-то какая разница?

А почему меня это должно волновать?

Вы же что-то публично высказали. Если вам пофиг как ваши публичные высказывания трактуются, то вопросов нет.

Развлечение в комментариях на недосайтике для хомяков — это не публичное высказывание.

Согласно действующему законодательству в РФ - публичное. Учим матчасть.

Мне было насрать на действующее законодательство РФ даже тогда, когда оно меня хоть каким-то боком касалось.

свою пишет лет 10

Строго говоря, первая версия появилась в апреле 2002-го года.

Как на мой взгляд - всё перечисленное есть дело вкуса или привычки. В Rust есть куда более раздражающие вещи. Например, необходимость инициализации буфера или применения unsafe, в тех случаях, когда нет никакого смысла этот буфер инициализировать. Ну кто мешает ввести аттрибут параметра RequireWriteBeforeRead?

Потому что не инициализованные данные не могут быть проверенны на safe компилятором, так что абсолютно логично что данный блок выносится в unsafe с MaybeUninit

не могут быть проверенны на safe компилятором

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

Если добавить для параметра атрибут RequireWriteBeforeRead или нечто подобное, то компилятор будет знать, что в этот буфер можно безопасно писать без инициализации, а чтение из него допустимо только после записи.

Так а чем вас MaybeUninit не устраивает в этом случае? функция write вполне себе safe. Или вы хотите чтобы комплилятор сам вычислял была ли сделана запись до первого чтения или нет? Так это задача точки остановки, тут не будет решения.

Так а чем вас MaybeUninit не устраивает в этом случае?

Тем, что он unsafe. И если для pet-проектов это значение не имеет, то в случае промышленных проектов мне на каждый unsafe нужно для заказчика писать страницу в Confluence с обоснованием того, почему был использован unsafe.

Я уже молчу о том, что, например, в plrust unsafe вообще запрещен, по понятным причинам.

функция write вполне себе safe

При чем тут вообще write? Как раз писать куда-то неинициализированные данные явно не стоит.

Или вы хотите чтобы комплилятор сам вычислял была ли сделана запись до первого чтения или нет?

А он и так это делает для любой переменной. Я же говорю лишь об атрибуте, позволяющем отложить эту запись до кода в вызываемой функции.

При чем тут вообще write? Как раз писать куда-то неинициализированные данные явно не стоит.

Вы прочитайте документацию. MaybeUninit::write как раз устанавливает/инициализирует данные для MaybeUninit, а не пишет содержимое куда. Ровно то, что вы хотите, "в этот буфер можно безопасно писать".

Sets the value of the MaybeUninit<T>.

This overwrites any previous value without dropping it, so be careful not to use this twice unless you want to skip running the destructor. For your convenience, this also returns a mutable reference to the (now safely initialized) contents of self.

https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.html#method.write

А он и так это делает для любой переменной. Я же говорю лишь об атрибуте, позволяющем отложить эту запись до кода в вызываемой функции.

Нет, не делает. В расте обычная переменная не может быть неинициализирована. Вы просите чтобы компилятор считал safe кодом чтение из потенциально неинициализированного буфера на основании того, что где-то там в какой-то ветке вашего кода произошла отложеная инициализация и это надо учесть на основании наличия "атрибута". Формально доказательство того, что инициализации всегда в вашей программе происходит до чтения, не возможно вывести на этапе комплияции(проблема точки остановки) и предлагаемый вами атрибут это попытка обмануть самого себя насчет безопастности исполнения.

Тем, что он unsafe. Нужно для заказчика писать страницу в Confluence 

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

MaybeUninit::write

Если Вы про него, то он ещё опасней, так как может приводить к утечке памяти.

В расте обычная переменная не может быть неинициализирована.

С какого перепугу? Пожалуйста.

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

Вы просите чтобы компилятор считал safe кодом чтение из потенциально неинициализированного буфера на основании того, что где-то там в какой-то ветке вашего кода произошла отложеная инициализация и это надо учесть на основании наличия "атрибута".

Я прошу, чтобы компилятор позволял передать буфер вызываемой функции до его инициализации. А в этой функции не позволял читать из него до того, как он будет инициализирован. Точно так же, как в примере выше он не позволит читать из переменной до её инициализации. Вот так.

Потому что вы хотите заниматься unsafe операцией.

А какие ещё варианты? Инициализировать буфер в ущерб производительности? Особенно глупо это выглядит, например, при инициализации буфера DMA при потоковом чтении с SPI или ADC.

Я прошу, чтобы компилятор позволял передать буфер вызываемой функции до его инициализации. А в этой функции не позволял читать из него до того, как он будет инициализирован.

Это нереально реализовать на уровне компилятора.

 Инициализировать буфер в ущерб производительности

Да, если ситуация требует. Контрпример - функция при чтении затерла реальными данными не весь буфер по какой-то причине, ну ошибка чтения например. Потом такие грязные данные уйдут дальше.

Это нереально реализовать на уровне компилятора.

Я уже дважды писал выше, как это можно сделать!

функция при чтении затерла реальными данными не весь буфер по какой-то причине, ну ошибка чтения например. Потом такие грязные данные уйдут дальше.

Так как буфер по определению используется многократно, то компилятор тут никак не спасёт от грязных данных от предыдущего использования буфера.

Я уже дважды писал выше, как это можно сделать!

Ваше решение это не как это сделать, а как отключить проверку.

представим такой код

let mut buffer = UninitBuffer::with_capacity(1024); 

if(rng.get() == 42){

  init_buffer(&mut buffer);
}

read_buffer(&buffer);

Как ваш атрибут должен помочь тут? Вам может казаться что в вашем случае поведение куда более детерминировано чем в этом примере, но на самом деле с точки зрения компилятора если объявление, инициализация и чтения буфера разнесены в разные скопы или уровни стека(а еще хуже в разные потоки), то это ровно такая же задача, которую в общем случае не возможно решить анализом AST.

Ваше решение это не как это сделать, а как отключить проверку.

Читайте внимательней. Не отключить проверку, а позволить перенести её в вызываемую функцию, явно указав атрибутом параметра, что он не инициализированный. А внутри этой функции этот параметр должен рассматриваться точно так же, как неинициализированное объявление локальной для функции переменной.

не возможно решить анализом AST

Решает же.

 А внутри этой функции этот параметр должен рассматриваться точно так же, как неинициализированное объявление локальной для функции переменной.

Так а как вызываемая функция внутри поймет что параметер был инициализирован? Или вы хотите чтобы она всегда ошибка компиляции выдавала?

Решает же.

Так где же.

Compiling playground v0.0.1 (/playground)
error[E0381]: used binding i is possibly-uninitialized
--> src/main.rs:8:16
|
4 | let mut
i:i32;
| ------ binding declared here but left uninitialized
5 | if rand::rng().random_range(0..100) == 42 {
| -------------------------------------- if this if condition is false, i is not initialized

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

Так а как вызываемая функция внутри поймет что параметер был инициализирован?

Из того же самого атрибута параметра в её описании.

Так где же.

Выдает ошибку, видя, что переменная может оказаться не инициализирована.

Из того же самого атрибута параметра в её описании.

Это будет атрибут, который говорит что передаемый параметер уже инициализирован и проставлять его будет программист? Где же здесь safe гарантия компилятора?

Выдает ошибку, видя, что переменная может оказаться не инициализирована.

Так он требует чтобы была инициализация во всех бранчах текущего скопа. Так же будет для вашего буфера требовать - чем вам это тогда поможет, вы же хотите инициализировать когда-нибудь потом, дальше по стеку.

Это будет атрибут, который говорит что передаемый параметер уже инициализирован и проставлять его будет программист?

Наоборот, отсутствие этого атрибута будет, как и сейчас, требовать чтобы параметр был инициализирован. А вот указание этого атрибута позволит вызывающей функции передавать неинициализированное значение, а вызываемой функции всегда считать этот параметр неинициализированным.

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

Да я его вообще могу никогда не инициализировать. Его, например, DMA при своей работе инициализирует. А функция чтения из DMA буфера будет в своем crate в unsafe блоке из него читать, на основании регистров статуса DMA канала. Там то вообще без вариантов, так как множество регистров МК вообще не подразумевают программной инициализации, а запись в них приведет к прерыванию по ошибке.

И атрибут будет называться unsafemem, например.

Та-дамс!

Да без разницы, как он будет называться. Главное, что он позволит безопасно не инициализировать. С гарантией, что компилятор не позволит читать из этого буфера до того, как туда будет что-то записано, но позволит передать его вызываемой функции.

 А функция чтения из DMA буфера будет в своем crate в unsafe блоке из него читать

А что ей тогда не позволяет читать в unsafe блоке из MaybeUninit?

Ничто не мешает. Мешает невозможность выделить все буфера при инициализации кода в МК без unsafe.

Извините, я все еще не понимаю. Почему нельзя буфер вот так выделить, без unsafe и инициализации нулям?

let buffer = MaybeUninit::<[u8; 500]>::uninit(); // аллоцирует неинициализированный блок памяти в 500 байт.

Потому что это уже не буфер, а структура типа MaybeUninit, содержащая, кроме всего прочего, буфер. И извлечь из ней неинициализированный буфер можно только при помощи

unsafe { buffer.assume_init() };

Ну технически она не содержит ничего кроме буфера. И вы же писали про "невозможность выделить все буфера при инициализации", а не про извлечение неинициализированный буфера без unsafe.

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

С таким подходом Вы не то что в 2К RAM ATMega328 не впишетесь. Вам даже 20К RAM STM32F103 не будет хватать.

Оно ж в стеке, откуда издержки?

Не факт. Зависит от множества обстоятельств. Но даже в этом случае есть оверхед.

Я не припомню обстоятельств, способных неявно перенести значение из стека в кучу в Rust, а из оверхеда припомню только stack pointer

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

Проблема стека в том, что контролировать его переполнение намного сложнее, чем динамическое выделение памяти. Защиты памяти на МК, обычно нет.

А, ну так бы сразу и сказали, что вам нужна пародия на аллокатор, MaybeUninit здесь не выглядит подходящим инструментом

Не пародия, а полностью управляемое распределение памяти с минимальными издержками. Следует понимать, что на МК освобождать ранее аллоцированную память требуется редко.

MaybeUninit здесь не выглядит подходящим инструментом

Почему? Сам трейт позволяет через unsafe объявить инициализированным что угодно. Меня не устраивает как наличие unsafe, так и его последствия, в виде потенциальной возможности чтения из неинициализированного буфера.

Почему?

Потому что его интерфейс предполагает инициализацию связанного куска памяти целиком, а не по частям (а вы, если я правильно интерпретирую предыдущий комментарий, хотите по частям)

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

Во-первых причем тут динамическая память если пример на стеке аллоцирует, во-вторых это enum и согласно layout энамов раста она занимает ровно 500 байт, в размер самого буфера, присловутые zero-cost abstraction. Даже если вы сделаете Box, у вас точно так же будет 500 байт в куче + оверхед на чанк вашего аллокатора и 8 байт на стеке для указателя.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=4ebda6aafb749be9144b39640cf538b1

Вы путаете sizeof с реальным размером объекта в памяти. Там есть ещё скрытый tag, который дает оверхед в четыре байта на 32-х битной платформе в случае буфера для DMA из-за необходимости выравнивания.

Странное требование.

Ведь любой unsafe можно закопать в “безопасную” функцию.

А нужно на явный неявный?

Транзитивный вызов учитывается ?

Положим Rc использует внутри unsafe, им можно пользоваться ?

любой unsafe можно закопать в “безопасную” функцию

Попробуйте это сделать, например, в plrust (trusted).

Господи, при чем тут plrust, который по сути DSL и в котором нужно малюсенькое подмножество раста, в которое, очевидно, вообще никаким боком не попадает unsafe?

подмножество раста

Что никак не отменяет необходимость использования в нём буферов.

Ну ладно, если не нравится plrust, пусть будет выделение буферов на любом МК. Надеюсь, не нужно объяснять, почему на МК память должна выделяться при инициализации, а не в процессе работы?

Вот при инициализации и надо ее занулять.

Что не просто отрицательно влияет на производительность, но может кумулятивно приводить к катастрофическим последствиям из-за слишком долгой инициализации.

ну так это значит что отвественность за корретную иницилизацию здесь несет программист и компилятор не может вам дать здесь гарантий. А значит это unsafe код. У вас на МК в любом случае будет no_std и кастомный аллокатор, который без unsafe вы не напишите.

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

А если речь идет о батарейном питании от CR2032 и тактовой частоте в десятки килогерц, то время инициализации даже нескольких килобайт очень заметно даже на глаз.

Я уже молчу о случаях SPI RAM.

Ну значит положите в крейт аллокатора апи для создания не инициализированный буфферов и оберните это в структуру с публичным safe методами. try_read -> Result<&[u8]>

После чего компилятор уже никак не сможет контролировать попытку чтения из буфера до записи в него.

Это к языку не относится. Значит меняйте железо, на то, которое побыстрее обнуляет память.

В принципе по дефолту всегда сегмент данных - нули, не пойму откуда высосана проблема.

Это к языку не относится.

Иными словами, Вы пытаетесь утверждать, что Rust не способен заменить C/C++ на МК?

Значит меняйте железо, на то, которое побыстрее обнуляет память.

За чей счет банкет? CR2032 стоят на порядок дешевле, чем более емкие альтернативы. И это даже не говоря о габаритах.

В принципе по дефолту всегда сегмент данных - нули

Откуда Вы это взяли? Назовите хотя бы один МК, по умолчанию инициализирующий RAM или, тем более, SPI RAM.

МК/ПЛК это программно-аппаратный комплекс, в который входит - железо, возможно ОС, рантайм исполнения/компилятора и язык программирования. Тут предъявлять претензии только к одному компоненту бессмысленно, надо смотреть все в комплекте.

И да, разные комбинации вполне могут по-разному себя вести, за всю Одессу говорить нельзя.

Иными словами, Вы пытаетесь утверждать, что Rust не способен заменить C/C++ на МК

Это отдельный независимый от темы вопрос. Но лично я считаю Раст слишком нишевым - не для всех задач он хорошо зайдет.

не для всех задач он хорошо зайдет

На данный момент, я достаточно широко использую Rust на MK. И больше всего мне мешает именно необходимость unsafe для передачи неинициализированных буферов вызываемым функциям. Остальное вполне можно обернуть в библиотеки, локализуя unsafe только там.

Потому что в данном случае uninit_buffer будет занимать лишние 4 байта на 32-х битной платформе из-за необходимости выравнивания буфера для DMA. Если при этом размер буфера тоже 4 байта, то получим двукратный перерасход столь дефицитной на МК RAM.

Не будет, MaybeUninit всегда будет занимать столько же, сколько внутрений тип, в этом собственно и есть его отличие от Option<T>, его ровно для этого сделали. Это не структура с полем на котором может быть паддинг, это не алгебраический тип с тэгом-дискреминатором, это тип-псевдоним, который не существует за пределами исходного кода. Между MaybeUninit<[u8; 4]>> и [u8; 4] нет никакой разницы с точки зрения лейаута по памяти в скомпилированной программе, может сами проверить и убедиться.

Я вижу, что MayBeUnit - union. А union обязан содержать tag, определяющий, какое из возможных значений он в данный момент содержит.

#[repr(transparent)] в его декларации вы видите? что по-вашему это означает?

А union обязан содержать tag

Чем по-вашему в расте union отличается от enum?

Я вижу, что MayBeUnit - union. А union обязан содержать tag, определяющий, какое из возможных значений он в данный момент содержит.

Во вчерашней ветке я пытался избегать этого, но всё-таки вынужден констатировать, что вы не знаете Rust

Unions have no notion of an “active field”. Instead, every union access just interprets the storage as the type of the field used for the access.

(поэтому использование union это тоже unsafe)

Идея интересная но это как бы подмножество языка получается.

Кстати, Rust не sound это значит, что можно и в безопасном коде написать код с проблемой безопасности.

Почему подмножество? Просто на каждую функцию накладывается ряд вполне разумных ограничений, так как она выполняется в среде PostgreSQL. Или поясните, почему обязательное наличие такой конструкции превращает язык в подмножество:

#![forbid(unsafe_code)]

Если мы говорим о МК, ОС и тп, то придётся использовать no_std, которое действительно превращает Раст в подмножество.

И достаточно узкое, т.к без R/RefCell что-то сложное не напишешь.

Если вы имели в виду RefCell, то он доступен даже в no_std

Во-первых, я дал ссылку на plrust, который к МК не имеет никакого отношения. Во-вторых, если на AVR действительно без no_std никуда, то на ESP32 вполне можно и std использовать.

без R/RefCell что-то сложное не напишешь

С чего Вы взяли, что RefCell не доступен в no_std?

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

В ностд отваливается слишком много, даже если рефцелл доступна.

Как говорится, удачи обеим сторонам конфликта =)

На самом деле в no_std отваливается очень мало из того, что действительно востребовано на МК. Это даже не касаясь того, что для ESP32 std подразумевает работу через ESP-IDF, написанном на C, тогда как no_std реализуется целиком на Rust.

В ностд отваливается слишком много, даже если рефцелл доступна.

И что же там такое отваливается в случае, если операционной системы как таковой просто нет? Даже HTTP сервер на no_std замечательно реализуется.

На самом деле, это решаемая со временем задача - убрать из std паники и аллокации, хотя бы из таких частей, без которых - никак. Сейчас предлагаются им заменяющие аналоги.

Даже ХХХХ на no_std замечательно реализуется

Ну хватает функциональности - так пишите.

А то жалуются то на то, то на это =)

дело вкуса или привычки

Ну если рабочий язык один, то привычку, наверное, упомянуть уместно. У меня их значительно больше. И когда чтобы просто читать комментарии в коде мне приходится делать специальную команду в виме «сдвинуть вьюпорт на три строки влево/вправо» — это неимоверно бесит.

И нет, топорные макросы и кривой генератор документации — это не вкусовщина.

Ну если рабочий язык один, то привычку, наверное, упомянуть уместно. У меня их значительно больше.

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

у меня и сформировался мой личный взгляд на то

А у меня — мой, и он, насколько могу судить, от вашего отличается :)

Мотивируйте тогда хотя бы. Макросы пишутся на том же Rust. Не нравится какой-то стандартный макрос - пишите свой. А уж добавить в build.rs вызов чего угодно для генерации вообще чего душа пожелает - не велика проблема.

Угу. «Хлеба нет — пусть едят пирожные!»

Не понял, при чем тут это? Какие ограничения языка Rust Вам мешают писать свои макросы или гонять любимый препроцессор?

Это некорректная постановка вопроса. Мне ничего не мешает, мне доводилось писать на КОБОЛе, ассемблере и даже похапе. Я способен добиться результата. Но для меня этого недостаточно.

Я хочу, чтобы мне было удобно. Чтобы обо мне позаботились авторы языка. Или чтобы они хотя бы двигались в этом направлении.

Отсутствие в серьезном языке, спроектированном в XXI веке родного AST (а не криво прикрученного сбоку) — это непростительная халатность, которая затрудняет работу с макросами до отвращения. У меня нет стокгольмского синдрома, поэтому я, с вашего позволения, продолжу называть кривую реализацию — кривой.

Вы сами-то пробовали писать мало-мальски сложные макросы? Потому что я пробовал, и результат даже доступен по ссылке. Если вы настаиваете на продолжении выяснения «что не так» — покажите ваши для начала.

Я хочу, чтобы мне было удобно. Чтобы обо мне позаботились авторы языка. Или чтобы они хотя бы двигались в этом направлении.

Добро пожаловать в OpenSource. Всё в Ваших руках. Делайте свой crate. Это же не PR в mainstream, где замучаешься обосновывать его необходимость и качество его кода.

Вы сами-то пробовали писать мало-мальски сложные макросы?

У меня просто не было такой необходимости, так как Rust использую только на МК или в plrust, где "мало-мальски сложные макросы" просто не требуются.

Добро пожаловать в OpenSource.

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

Всё в Ваших руках. Делайте свой crate.

Вы читать умеете? Как я крейтом поправлю им родовую травму с AST?

Это же не PR в mainstream

У меня полно́ PR-ов в mainstream нескольких языков.

У меня просто не было такой необходимости

Это очень заметно.

вагон и маленькая тележка коммитов

Если для Вас размер достоинства имеет значение в дискуссии, то Вы явно перепутали Хабр с Пикабу )))

Как я крейтом поправлю им родовую травму с AST?

Например, так же, как это делаю я с кодом T-SQL, PL/SQL или plpgsql при помощи m4.

размер достоинства

Это был ответ на идиотское, абсолютно нерелевантное, диванное «Добро пожаловать в OpenSource».

так же, как это делаю я с кодом T-SQL, PL/SQL или plpgsql при помощи m4

Я не ясновидящий, ваши фантазии читать не умею. А кода, чтобы его показать, у вас — дайте угадаю — нет.

идиотское

Вы точно перепутали Хабр с Пикабу )))

А кода, чтобы его показать

Что показывать? Вызов m4 в make или пример макроса на нём? Так и то и другое настолько элементарно, что не стоит каких-либо демонстраций.

Вы точно перепутали Хабр с Пикабу )))

Лёшку(он же @cupraer, он же @chapuza, он же @matiouchkine) уже два раза ссаным тряпками выгоняли с хабра за быдлячество, вранье и ничем необоснованное раздутое самомнение, но ему все мало, он все время возвращается.

За что я обожаю этот гадюжник, раз в пару месяцев обязательно вылезает какой-то хрен с горы, которого ты вообще в первый раз видишь и через секунду про его существование забудешь, а он, оказывается, — сталкер!

Всё про тебя знает, помнит, бережно хранит. Карму я уже плюсанул, может с хорошим психологом познакомить?

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

Вести заметки о случайно встреченных людях в интернете, тщательно дополняя их по мере поступления новых данных — это и есть сталкерство.

Мило, как у вас все напускные щечки сдулись, и наружу вылезло мытищинское быдло.

Тоже решил пощупать, и многие моменты из статьи знакомы. Особенно про макросы до сих пор не понимаю, почему всё так сложно выглядит. Про строки тоже невдуплил, когда впервые увидел {} без имён. После привычных интерполяций вообще странно, но при этом есть ощущение, что половина этой кривотуры просто непривычно, а не потому что язык какой-то не такой, не надо просто его выставлять как спасение человечесва(замена плюсам). Rust прям очень по-своему всё делает.

Rust прям очень по-своему всё делает.

Кроме borrow checker’а в расте никаких свежих идей нет, поэтому то, о чем я высказался — не вкусовщина, а как раз обобщение кругозора.

Borrow checker главная фича да, но если отбросить вкусовщину, то можно выделить безопасные enum+match, Option/Result вместо null, trait’ы с generics и безопасной многопоточностью. Синтаксис непривычный, но идеи реально полезные. изучать

Это всё лет тридцать назад появилось в Хаскеле, многопоточность — 40 лет назад в эрланге.

многопоточность — 40 лет назад в эрланге

Многопоточность? o_O

Разве у легковестных процессов в Erlang-е общая разделяемая память с единым адресным пространством?

Между многопоточностью и «общей разделяемой памятью с единым адресным пространством» нет вообще ничего общего. Ни единого пересечения.

Многопоточность -- это параллельное выполнение потоков (тредов) исполнения внутри одного процесса и внутри единого адресного пространства этого процесса. Этим многопоточность принципиально отличается от многопроцессности, где параллельно выполняются независимые процессы, у каждого из которых собственное адресное пространство.

Из-за этой особенности многопоточности присущи проблемы, в принципе отсутствующие в многопроцессности.

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

Но если в вашей вселенной многопоточность означает что-то другое, то было бы интересно узнать что именно.

А параллельное выполнение потоков (тредов) исполнения внутри разных процессов как называется, даже стесняюсь спросить?

в вашей вселенной многопоточность означает что-то другое

В моей вселенной (а также во вселенной всех без исключения людей, которые пользуются общепринятой терминологией, а не изобретают свою, удобную под каждую конкретную оказию) — многопоточность (multithreading) — это выполнение многих потоков (тредов) параллельно. На адресные пространства этот широкий термин не распространяется (что, например, следует прямо из его транскрипции).

А параллельное выполнение потоков (тредов) исполнения внутри разных процессов как называется, даже стесняюсь спросить?

Многозадачность. То о чем вы говорите -- это гранулярность диспетчеризации у шедулера ОС: является ли единицей диспетчеризации тред или же процесс. При этом возможна поддержка многопоточности, но единицей диспетчеризации в ОС будет не тред, а процесс.

На адресные пространства этот широкий термин не распространяется (что, например, следует прямо из его транскрипции).

Сам по себе тред существует только внутри процесса. Соответственно, соседство нескольких тредов в одном адресном пространстве -- это неотъемлимое свойство тредов.

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

Я искрене удивляюсь, когда встречаю людей с ЧСВ выше моего. Это, блин, редкая редкость и выдающееся событие. Но ваше ЧСВ настолько выше, что я шокирован.

Да перестаньте вы спорить ради спора, ради всего святого, и просто отвлекитесь на секунду от моего ЧСВ, расслабьтесь, не знаю, стакан накатите.

Представьте гипотетическую ситуацию: я руками, на голом си, написал код, который при помощи велосипедного менеджера управления запускает восемь потоков в двух процессах и что-то считает там, майнит, или просто залупился (от англ. loop) и висит.

Моя программа будет многопоточной?

Да перестаньте вы спорить ради спора

Не понимаю, где вы увидели спор. Я вам вопросы задаю, вы свой ЧСВ демонстрируете вместо диалога.

я руками, на голом си, написал код, который при помощи велосипедного менеджера управления запускает восемь потоков в двух процессах

Поскольку мультитрединг существует только внутри одного процесса, то как только вы внутри процесса так или иначе запускаете два или более тредов, у вас появляется многопоточность.

Что происходит в другом процессе отношения к разговору не имеет.

И как Вы представляете себе Haskell или Erlang без сборщика мусора? У них совершенно иная область применения, чем у Rust, который можно использовать на MK или в ядре ОС.

При чем тут вообще сборщик мусора? Вы прочитали ветку, в которую впёрлись со своим бесценным комментарием?

При том, что неразумно сравнивать языки со сборщиком мусора и с полным контролем программиста за распределением памяти. У них разные области применения.

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

Какой смысл сравнивать идеи, которые только из-за наличия или отсутствия сборщика мусора требуют совершенно разных подходов к их реализации? Включая и обоснованность такой реализации.

И как Вы представляете себе Haskell или Erlang без сборщика мусора?

Как Ivory, например. Это хаскельный eDSL, но там нет GC, и всё компилируется либо в статические аллокации, либо в стек (который в языке торчит плюс-минус как регионы).

Sign up to leave a comment.

Articles