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

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

Документация

▸ прицепить текст на маркдауне к документации — фактически невозможно, есть костыли типа https://github.com/Geal/cargo-external-doc и директива #![doc = include_str!("path/to/file.md")], которая всё портит (и бибикает).

Как надо: в Cargo.toml должен быть список файлов, которые я хочу превратить в отдельные страницы документации (видимые в левом меню на https://docs.rs)

Строки

▸ интерполяцию строк писал какой-то герпетолог, ей-богу (или сиквелянт); неужели целесообразно заставлять читателя индексировать набор параметров глазами: "{} + {} = {}", a, b, c?

Как надо: "{a} + {b} + {c}"

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

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

fn handle_event(event: Self::Event, state: Self::State) -> StateTransition<Self> {
  match (state, event) {
    (DoorState::Locked, DoorEvent::Button(digit)) => { … }
    (DoorState::Open, DoorEvent::Lock) => { … }
    _ => StateTransition::keep_state(Err("Invalid event".into())),
    }
}

Как надо:

fn handle_event(event: Self::Event, state: Self::State) -> StateTransition<Self> 

fn handle_event(DoorState::Locked, DoorEvent::Button(digit)) { … }
fn handle_event(DoorState::Open, DoorEvent::Lock)) { … }
fn handle_event(_, _) { StateTransition::keep_state(Err("Invalid event".into())) {}

Да и вообще, паттерн-матчинг после эрланговского выглядит убогим, я даже в Cure уже успел что-то подобное, но я не буду заявлять о готовности к продакшену, пока не реализую полную декомпозицию от x = 1; 1 = x до {foo: {bar: value, baz: 42}} when value > 42 = struct. И вообще, хорошо бы иметь свой оператор для матча, как = в эрланге.

Ссылки

▸ почему ссылки &foo — не поведение по умолчанию?! Особенно учитывая, что в качестве аргумента функции для модификаций (мутабельно) можно передать только ссылку &mut foo? Ну ё-моё, сделайте всё ссылками и специально заставьте крохоборов вызывать оператор копирования если нужно что-то передавать по значению (в 2025 году это нужно трём с половиной людям, которые все равно предпочтут расту — ассемблер). А ведь есть еще и «умные указатели»!

Как надо: самый простой вариант — по умолчанию всё ссылка; если нужен доступ по значению — его надо указывать эксплицитно: fn foo(mut self, val event: …).

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

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

▸ метапрограммирование накостылено сбоку (что является, конечно, следствием отсутствия прямого доступа к AST). Какие-то метапеременные, пять разных типов макросов, нечитаемый синтаксис (код на расте в принципе легко проглатывается с листа даже теми, кто видит его впервые; первый же пример макроса в документации выглядит вот так: macro_rules! ambiguity { ($($i:ident)* $j:ident) => { }; }. Единственная возможная реакция человека, столкнувшегося с этим перлом, — WTF? Вы издеваетесь, что ли? Почему нельзя было сделать по-человечески? Что с unquote — тоже неясно. В каком-то смысле #var отвечает за это, но я, даже реализовав свой макроатрибут, так до конца и не понял, как работает механизм quote/unquote.
▸ с какого-то хрена для макросов нужен отдельный крейт, такого гигантского WTF я со времен изучения настройки новелловской сети не помню.

Как надо: как в эликсире (на худой случай — как в эрланге). Язык компилируемый, два прохода, гигиена и ультимативный эксплицитный unquote, в котором может быть практически любой код, а не то, что проглотит #….

Тесты

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

Как надо: если не пропадёт запал, следующим шагом портирую mox и nimble_ownership.

Комментарии

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

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


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

Комментарии (17)


  1. Dhwtj
    06.12.2025 10:40

    {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(())),
        }
    }

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


    1. cupraer Автор
      06.12.2025 10:40

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

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

      ❯ 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


      1. Dhwtj
        06.12.2025 10:40

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

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

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


      1. SergeiMinaev
        06.12.2025 10:40

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

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

        Так можно:

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


        1. cupraer Автор
          06.12.2025 10:40

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


    1. cupraer Автор
      06.12.2025 10:40

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


    1. cupraer Автор
      06.12.2025 10:40

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

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


  1. BeardedBeaver
    06.12.2025 10:40

    1. cupraer Автор
      06.12.2025 10:40

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


      1. Dhwtj
        06.12.2025 10:40

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


  1. codecity
    06.12.2025 10:40

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


    1. cupraer Автор
      06.12.2025 10:40

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

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


      1. Dhwtj
        06.12.2025 10:40

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

        AST

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


        1. cupraer Автор
          06.12.2025 10:40

    1. Dhwtj
      06.12.2025 10:40

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

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

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


      1. cupraer Автор
        06.12.2025 10:40

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

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

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


        1. Dhwtj
          06.12.2025 10:40

          Я тут разбираю 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(()))
              }
          }

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