Так ли токсичен синтаксис Rust?

    fn main() {
      println!("Hello, Rust!");
      println!("... Goodbye!");
    }

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


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

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

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

    Сначала хотел указать вам на ошибку, но потом понял, что "синтоксис" в данном контексте, нужно понимать как "токсичный синтаксис". Вот это именно то, чего мне не хватало что бы охарактеризовать раст.
    Теперь я точно знаю: Раст — это ЯП с ТОКСИЧНЫМ СИНТАКСИСОМ!

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

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

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


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


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


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


    Фигурные скобки


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


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


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


    if (test(x))
        foo(x);
        bar(x);

    Должен ли bar вызываться только тогда, когда test завершился успешно? По правилам языка — нет, так как отсутствуют фигурные скобки, обозначающие блок. Но такое форматирование кода затрудняет для человека понимание этого. Все потому, что имеется лишняя избыточность в обозначении блоков: скобками — для парсера, отступами — для человека, и они могут не совпадать. Поэтому в Python решили отказаться от этой избыточности:


    if test(x):
        foo(x)
        bar(x)

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


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


    Даже без автоформатирования, Rust решает проблему случайных отступов радикальным образом:


    if test(x) {
        foo(x);
    }
    bar(x);

    В конструкциях if, else, for, while и так далее, блок требуется обязательно! Сокращенная, бесскобочная запись тела у данных операторов в языке просто отсутствует. С одной стороны, это "загрязняет" фигурными скобками простые условия, но отчасти это компенсируется тем, что теперь круглые скобки вокруг условного выражения становятся избыточными и их можно убрать.


    Вообще, в Rust блоки очень важны, они являются самостоятельными программными объектами с особыми свойствами.


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


    ...
    {
        let mut data = data.lock().expect("Mutex should be lockable");
        data.read();
        data.write();
    }
    ...

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


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


    let value = {
        let a = 1;
        let b = 2;
        a + b
    };

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


    let value = if x < 42 { -1 } else { 1 };

    А многие функции и замыкания избавляются от лишнего синтаксиса с оператором возврата:


    fn add_two(a: isize) -> isize {
        a + 2
    }

    вместо


    fn add_two(a: isize) -> isize {
        return a + 2;
    }

    Для специальных блоков, таких как unsafe и async, появляется возможно оборачивать ими только часть выражения, что в ряде случаев удобно и делает код нагляднее:


    let a = 5 + unsafe { an_unsafe_fn() };

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


    Точка с запятой


    Отдельного рассмотрения заслуживает то, как работает точка с запятой в Rust. Многим не нравятся языки, которые требуют окончания инструкций точкой с запятой: зачем, если и так перевод строки может означать завершение инструкции? Отказ от точки с запятой оправдан, если в языке любая строка по умолчанию является инструкцией, как в Python. Если же язык использует концепцию "все есть выражение", которая расширяет его выразительные возможности, то без специального терминатора, по которому можно отличить строку-инструкцию от строки-выражения, удобным в использовании может быть только язык с динамической типизацией, такой как Ruby. Да и в таком языке в ряде случаев подход "все есть выражение" приводит к неудобствам:


    class Foo
      def set_x val
        @val = val
        nil
      end
    end

    Здесь возникает необходимость вставлять nil в конце блока, чтобы set_x возвращал пустое значение, а не результат выражения @val = val, то есть не значение val. Если это еще терпимо, то ситуация становится совершенно неприемлемой в случае статических языков и использования ветвлений:


    if x < 42 {
        foo(x)
    } else {
        bar(x)
    }

    Здесь foo возвращает число, но мы его не используем, а bar возвращает "пусто". Какого типа должно быть значение всего условного выражения?


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


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


    if x < 42 {
        foo(x);
    } else {
        bar(x)
    }

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


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


    let message = "Hello!".to_string();
    message;
    
    println!("{}", message); // error[E0382]: borrow of moved value: `message`

    Так что инструкция a; становится аналогом drop(a). Это нужно помнить, чтобы не удивляться, когда компилятор откажется компилировать инструкцию, в которой производится попытка сохранить ссылку на перемещенное внутрь нее значение.


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


    Постфиксная запись типа


    Иногда простота синтаксиса является кажущейся. Например, частенько задается вопрос: зачем в Rust используется постфиксная запись типа, ведь это усложняет запись, еще и приходится отделять имена от типа с помощью двоеточия, тогда как префиксная запись, принятая в C/C++, этого не требует и выглядит чище. На самом деле префиксная запись чище только в ряде простых случаев использования, но в более сложных случаях она изрядно запутывает и парсер, и человека.


    Вообще, какой следует выбрать объективный критерий простоты синтаксиса? Мне видится, что немаловажно для оценки простоты синтаксиса является простота его парсера. Простой парсер может стать важным преимуществом языка программирования, так как он ускоряет время компиляции, разбор исходных файлов в IDE и утилитах анализа и автоматического преобразования кода (статические анализаторы, автоформатеры, авторефакторы и пр.). Кроме того, простота разбора синтаксиса также важна и для человека: во-первых, человеку тоже приходится "парсить" текст программы при ее чтении, а во-вторых, однозначность синтаксиса упрощает поиск нужных строк по кодовой базе, например, среди множества проектов на GitHub.


    Рассмотрим пример объявления переменной определенного типа.


    В Rust:


    let i: u64;

    В C++:


    uint64_t i;

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


    Прочитав i в i: u64, парсер уже знает, что i — это имя переменной, прочитав : он знает, что дальше идет имя типа, прочитав u64 он знает, что это имя типа, даже не сверяясь со списком имеющихся типов (то есть не заглядывая в семантику). Такой подход избавляет от необходимости декларировать тип до того, как будет объявлена переменная этого типа. В C/C++ из-за этого приходится отдельно делать объявления, отдельно определения, и иногда изменение порядка деклараций может изменить семантику.


    Для человеческого восприятия преимущества "паскалевской" записи раскрываются в сложных случаях:


    int (*fns[16])(int * const * p);

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


    Синтаксис же, принятый в Rust, отделяет обозначение типа от имени переменной:


    let fns: [fn(*const *mut isize) -> isize; 16];

    Обозначение типа при этом всегда одинаково, вне зависимости от того, указано ли оно в аннотации типа в инструкции let, в вызове size_of или в объявлении функции как тип возвращаемого значения.


    Ну и стоит сказать, что "паскалевская" декларация упрощается в случае автовыведения типа:


    let a = foo();

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


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


    Зачем вообще нужен let?


    Иногда let ругают за его избыточность и ненужность. Но обычно это делают те, кто не идет дальше элементарных примеров объявления переменных в Rust. Дело в том, что инструкция let занимается не объявлением переменных, а сопоставлением выражения с образцом (паттерном):


    let PATTERN: EXPR_TYPE = EXPR;
    
    let PATTERN = EXPR;

    В аннотации типа, если она требуется, указывается тип значения выражения справа, а не тип отдельных переменных, которые могут вводиться внутри паттерна (а могут и не вводиться). При этом let также используется в условиях if и while, если вместо истинности логического выражения там необходимо проверить соответствие выражения образцу:


    if let PATTERN = EXPR {
        ...
    }
    
    while let PATTERN = EXPR {
        ...
    }

    Сам синтаксис образца PATTERN общий для всех мест, где он может использоваться: помимо указанных let, if let и while let, это также оператор match, for и аргументы функций и замыканий (там уже нет надобности в отдельном слове let, так как ничего другого, кроме образца, использовать в этих местах нельзя).


    struct Foo {
        a: i32,
    }
    
    let Foo { a: mut b } = Foo { a: 25 };

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


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


    let a;
    
    a = 25;

    Наконец, Rust поддерживает затенение переменных:


    let a = 25;
    // `a` имеет числовой тип
    
    let a = "hello";
    // теперь `a` имеет строковый тип

    Во всех этих случаях нужно явно отличать let a или let a = 25 от просто a или a = 25, и ключевое слово let прекрасно с этим справляется.


    Сокращения в ключевых словах


    Уж наверное только совсем ленивые критики синтаксиса Rust не "проехались" по сокращениям в ключевых словах. Зачастую дело выставляют таким образом, что Rust форсирует сокращения и сплошняком только из них и состоит. Но на самом деле из 40 ключевых слов языка сокращениями являются только 7:


    fn, mut, mod, pub, impl, dyn, ref

    При этом dyn и ref используются крайне редко. Дополнительно есть еще 5 коротких ключевых слов (в две и три буквы), которые не являются сокращениями:


    if, as, let, for, use

    Все остальные слова — длиннее. Некоторые из них сделаны длинными намеренно, например continue и return: они используются редко и должны быть хорошо заметны в коде, хотя первоначальные версии языка вместо них использовали слова cont и ret.


    Тем не менее, больше всего претензий возникает к ключевому слову fn. Предлагают обычно либо его убрать совсем, либо заменить на function.


    На самом деле fn — весьма неплохой выбор для достижения компромисса между двумя крайностями. Наличие длинного слова function особо не помогает при чтении кода, обычно определение функции и так заметно по остальной ее сигнатуре и обязательному блоку с ее телом. Оно только оттягивает на себя внимание от имени и аргументов функции. К тому же оно занимает слишком много места, что плохо сказывается не только на скорости набора, но в ряде случаев и на чтении кода, например, в мессенджерах.


    Но и отсутствие ключевого слова — тоже плохо, так как в этом случае возникают ситуации, когда становится невозможно однозначно отличить функциональный тип от других элементов языка:


    let a: fn(i32, String); // `a` - указатель на функцию с двумя аргументами
    let b: (i32, String);   // `b` - кортеж из двух элементов

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


    Хорошо, но почему бы не выбрать сокращение подлиннее, такое как fun или func? Это довольно спорный вопрос даже в Rust-сообществе, но fn выглядит более нейтральным, так как не является другим самостоятельным словом и не имеет никаких посторонних значений по звучанию, так что оно хорошо ассоциируется с function и является минимально возможным его сокращением.


    Говоря о коротких ключевых словах и спецсимволах, стоит заметить, что код с ними становится плохо читаемым в том случае, если программист также выбирает короткие имена для объектов программы. В таком случае глаз "спотыкается", становится сложно отличить пользовательские имена от элементов языка. В случае же использования длинных имен, дискомфорта не возникает, наоборот, глазу становится проще отделять имена от синтаксических конструкций, что улучшает восприятие кода. Поэтому такие слова как fn, mut, mod, pub, impl, dyn и ref, после которых идут пользовательские имена, не затрудняют, а улучшают чтение программы, если при этом программист выбирает длинные осмысленные имена для своих объектов, а не увлекается сокращениями.


    Стрелка в сигнатуре функций


    Другая частая претензия к объявлению функций в Rust, это использование символа "стрелки" для разделения блока параметров от типа возвращаемого функцией результата:


    fn foo(x: i32) -> bool {
      ...
    }
    

    Кажется, что последовательнее тут тоже использовать двоеточие:


    fn foo(x: i32): bool {
      ...
    }

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


    let foo: fn(x: i32) -> bool;

    Против


    let foo: fn(x: i32): bool;

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


    В случае если функция не должна возвращать никакого значения, в Rust считается, что она возвращает значение "пусто" (), и тип возвращаемого значения можно не указывать. То есть


    fn foo() -> () {
        ...
    }

    равнозначно


    fn foo() {
        ...
    }

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


    Замыкания


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


    foo().map(|x| x + 2)

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


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


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


    foo().map(|_| ())

    Конструкция |_| () выглядит некрасиво. Она означает, что мы игнорируем входной аргумент замыкания и возвращаем из замыкания пустое значение. Подобный map полезен, когда нужно преобразовать одно значение типа Result в другое с заменой положительного значения возврата на "пусто". Однако добиться этого можно и более наглядным образом, просто передав функцию drop вместо замыкания:


    foo().map(drop)

    Двоеточия в пути


    В Rust используется двойное двоеточие :: в качестве квалификатора пространств имен, который разделяет сегменты в логических путях к элементам (items) языка, при том, что для доступа к полям и методам структур используется точка .. Это часто раздражает людей, привыкших к языкам, где для доступа к полям и методам используется тот же синтаксис, что и для доступа к элементам внутри пространства имен. Им кажется, что синтаксически разделять эти два обращения совершенно избыточно, и Rust использует подобное разделение либо в силу скудоумия своих создателей, либо из-за желания подражать C++.


    Однако на самом деле, предоставляемые Rust возможности требуют явного синтаксического разделения статических обращений xx::yy::zz и динамических xx.yy.zz, где результат будет зависеть от значения объектов xx, yy и zz во время выполнения. Возможны ситуации, когда программист должен использовать канонический путь для обращения к методу, когда ему нужно явно указать тип или типаж, на котором будет вызван данный метод:


    let foo = Foo;
    foo.bar();      // вызов метода `bar` из `Foo`
    
    Bar::bar(&foo); // вызов метода `bar` из реализации типажа `Bar` для `Foo`

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


    Foo::bar(&foo); // вызов метода `bar` из `Foo` у объекта `foo`
    Foo.bazz(&foo); // вызов метода `bazz` у созданного на месте объекта `Foo`

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


    Дженерики


    Хотя код с использованием угловых скобок для обозначения параметров обобщенных типов <T> выглядит более "шумно", чем код с использованием квадратных скобок [T], тем не менее в Rust используется вариант с угловыми скобками, потому что он однозначно отделяет тип от контекста его использования, тогда как конструкция [T] — сама является обозначением типа (среза).


    fn parse<F: FromStr>(&self) -> Result<F, F::Err> {
        // ...
    }

    Обобщенный метод parse на вход принимает тип F, реализующий типаж FromStr, и ссылку на экземпляр объекта &self, для которого реализуется данный метод. Возвращает же он обобщенный тип Result, в который передаются в качестве параметров типа F и ассоциированный с ним тип F::Err.


    Угловые скобки используются для работы с обобщенными типами как в контексте объявления обобщенных элементов, так и в контексте выражения при подстановке уже конкретных типов. И с этим связана, пожалуй, самая уродливая конструкция языка — жуткий мутант, внушающий первобытный страх любому, кто случайно заглядывает в глубины "синтОксиса" кода на Rust. Имя этому монстру — Турбофиш:


    "2021".parse::<usize>()

    Вот этот страшный зверь ::<> и есть Турбофиш. К сожалению оказалось, что от него совсем не просто избавиться: конструкция A<B>() выглядела бы логичнее и лаконичнее, но в контексте выражения парсер не может отличить ее начало от операции сравнения A < B. Отсюда и возникает необходимость дополнить угловую скобку разделителем сегментов в пути ::. Турбофиш очень знаменит в сообществе разработчиков на Rust, и если он когда-нибудь все-таки будет устранен (что вряд ли), то этот факт опечалит многих людей. По-своему он прекрасен, и к нему уже все привыкли.


    Времена жизни


    Имена времен жизни в Rust указываются с префиксом в виде одинарной кавычки: 'name. Не всем это нравится, но данный синтаксис самый лаконичный из всего, что было предложено. До его введения использовался синтаксис /&name и &name/, который гораздо хуже читается. Идея синтаксиса 'name была навеяна тем фактом, что близким аналогом параметра времени жизни является параметр типа, то есть указание времени жизни по-факту делает элемент обобщенным:


    struct StringReader<'a> {
        value: &'a str,
        count: uint
    }

    Идентификаторы с префиксом или суффиксом в виде одинарной кавычки используются в языках семейства ML. В частности, в OCaml запись 'name используется для обозначения переменной типа в обобщенных конструкциях. Rust продолжает эту традицию.


    Кроме того, в Rust нашлось еще одно интересное применение подобного синтаксиса — для задания меток:


    'outer: loop {
        loop {
            break 'outer;
        }
    }

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


    'a: {
        let x: &'a T = ...;
    }

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


    Макросы


    Синтаксис описания декларативных макросов справедливо критикуют за плохую читаемость, хотя сам по себе он довольно простой и понятный. Требование смешения обычного кода с командами макроса налагает серьезные ограничения на синтаксис команд и не в пользу повышения их читаемости. Проблема эта известна, и довольно давно уже ведутся работы по улучшению системы макросов в рамках реализации "macros 2.0". Но пока приходится довольствоваться тем, что есть. Хотя синтаксис макросов действительно часто выглядит токсично, спасает то, что он крайне минималистичен и прост: имеются идентификаторы фрагментов $name и конструкции повторения $(..)+, $(..)* и $(..)?. По сути — это все.


    Отдельно стоит упомянуть синтаксис обращения к макросу name!. Он проектировался с целью сделать имена макросов заметнее в потоке кода, чтобы визуально можно было сразу отличить вызов макроса от вызова обычной функции. Это важно, так как макросы расширяют синтаксис языка и принимают в качестве параметра код с пользовательским синтаксисом, то есть синтаксис самого выражения, а не его вычисленное значение. Восклицательный знак хорошо справляется со своей задачей, вызов становится заметным, но не настолько, чтобы перетянуть на себя все внимание (как происходит с другими, широкими символами, вроде @). Интуитивно восклицательный знак можно воспринимать как команду активного действия: макрос разворачивается во время компиляции, тогда как обычная функция остается в этом отношении пассивной. В похожем отношении восклицательный знак также используется в языке D при инстанциации шаблонов.


    Собираем все вместе


    Хорошо, допустим, каждый из элементов синтаксиса относительно неплох, но как они сочетаются все вместе? Разве их комбинация не превращает Rust-код в нечитаемое месиво из спецсимволов? Например:


    fn foo<'a, T: FromStr, I: IntoIterator<Item = T>, F: Fn(T) -> bool>(
        self: &'a Self,
        first: T,
        callable: F,
        iter: I,
    ) -> Result<&'a T, T::Err> {
        // ...
    }

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


    fn foo<T, I>(&self, first: T, callable: impl Fn(T) -> bool, iter: I) -> MyResult<T>
    where
        T: FromStr,
        I: IntoIterator<Item = T>,
    {
        // ...
    }

    Сигнатура метода foo уже не выглядит настолько страшно, как в исходном варианте. Она не идеальна, но сравнивая с тем, что было, все же заметен явный прогресс в улучшении читаемости. Видно, что алиасы, автовывод времен жизни, сахар для указания self, impl Trait в позиции аргумента, блок where и прочее, введены в язык не случайно и они сильно упрощают чтение действительно сложных мест, с непростой семантикой.


    Заключение


    Как видите, синтаксис Rust не так уж и плох. По крайней мере любой элемент синтаксиса продуман глубже, чем кажется при поверхностном взгляде и выбран именно таким по веским причинам соблюдения баланса простоты, единообразия и выразительности. Отчего же столько негодования и яростных воплей разносится по сети, насчет "токсичности" синтаксиса Rust? Я думаю главной причиной является объективная сложность и непривычность концепций, которые скрываются за этим синтаксисом, а также высокая информативная плотность кода на Rust. Новичку это вселяет ужас, а опытному Rust-программисту облегчает жизнь, так как расширяет его возможности, обеспечивает высокую степень явности и локализованности кода.


    Источники


    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 731

      +32
      Cтатья на несколько тысяч знаков о токсичности синтаксиса <чиво-бы-то-ни-было> — это апофеоз развития ИТ-сообщества 21 века, которое мы заслужили.

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

      Так же с удовольствием бы почитал о токсичности формы ручек для отверток, например.
        +13

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


        Если исходный код выглядит как стенография лекции по истории гностических ересей в Южной Франции на палимпсесте конспекта курса по топологии, это не будет способствовать популярности такого ЯП на рынке труда: работодатели будут считать найм разработчиков такого кода слишком дорогим, а рабочих мест для работников не будет хватать.

          +1
          Вы знакомы с, чтобы далеко не ходить, ABAP? Ни разу еще не встречал хронически безработного ABAPера. И ничего, работают, в основном, в командах.

          С другой стороны, синтаксис 1С, не то, чтобы ужасен, конечно, нет — он чудовищен… А проблем таких на рынке нет.

          Клингонский, по легенде, произошел из мертвого индейского языка, использовавшегося армейскими разведчиками США. Факт в том, что такие языки действительно использовались армейской разведкой: как раз для общения команды.

          Тут налицо классическая дилемма: шашечки или ехать. К сожалению, молодые-перспективные разработчики 21 года 21 века настолько заигрались в шашечки, будь то новомодные новоязы, фреймворки, пайплайны, проценты покрытия тестами, прочие аджайлы или — омг — рейтинги токсичности синтаксиса, считая их чем-то действительно важным, что ехать уже становится проблематичным.
            +11

            Почему молодые? Думаю, хейтеры синтаксиса Rust на Opennet вполне себе бородаты.

              +4
              Молодость и перспективность как субъективное состояние души. Есть наверняка и 15-летки — апологеты Фортрана и Лиспа, и кто их осудит.

              И да, само понятие «хейтер синтаксиса» вообще слабо коррелирует с опытом и мудростью, имхо.
                +7
                Думаю, хейтеры синтаксиса Rust на Opennet вполне себе бородаты.

                Ну я бородат. И мне не зашел синтаксис Rust. Правда на OpenNet я ничего не комментирую. Обстановочка не та.

                Впрочем, я и не хейтер. Вопрос в том, что любой синтаксис становится привычным при регулярном использовании. Не в синтаксисе дело. Он явно создан сторонниками Pascal'я. Не хорош и не плох. Я поплевался пока пробовал Rust в деле, но явного отторжения не вызвал. Часть на С выглядит лучше, часть на Rust'е. Меня больше python выбешивал. Пока не плюнул и не сказал — не мое. И то ругаться не буду. Просто это не мой инструмент.

                Вопросы к Rust они о другом. Впрочем, вот допишу статью потом обсудим… Лежит в черновиках…
                  +4
                  «Раст» создан поклонниками «Паскаля»? Что-то не могу в «Расте» буквально ничего от «Паскаля» вспомнить.
                    0
                    Тип после имени переменной, а не до, как в C++ и Java.
                      +8
                      ОМГ и это всё? А где «begin»/«end»? Присваивание через «:=», указатели через «крышечку»? На Си он в миллион раз больше похож, чем на Паскаль.
                        0

                        А что не как в TypeScript?

                          +1
                          Ну в Go то же самое, например. При этом он в гораздо большей степени C-like.
                          То же самое в Kotlin и Scala. Но там уж и говорить нечего :)
                      0
                      Да почему сразу хейтеры Rust'a? Удобный синтаксис, если его изучать, уделяя не меньше времени чем остальным. Просто менее популярный, потому больше людей его хейтят т.к. не пользуются им на постоянной основе.
                        –4
                        > Думаю, хейтеры синтаксиса Rust на Opennet вполне себе бородаты.
                        И не только на opennet, a вот я вчера побрился. И кстати как раз сегодня перед тем как спать пойти (после того как релиз залил), размышлял на тему об отсуствии статей на хабре об уродливости синтаксиса раста.
                        Целая куча замечательных, полезных и интересных идей так заштукатурена отвратительным уродливым синтаксисом, что на код даже смотреть не хочется. Дело не в сложности чтения этого кода, а в отвращении которое он вызывает при одном только взгляде.
                        Это что-то на уровне животных инстинктов, совершенно не рациональное, но это есть. Есть совершенно неполиткоректный факт, — многим людям сложно смотреть на калек и деформированных людей. Они даже взгляд отводят. Со временем это проходит, какая-то привычка вырабатывается. Так вот rust такое-же «биологически обусловленное» отторжение вызывает…
                          +3

                          Это скорее узость кругозора. Когда-то и меня сильно волновал вопрос синтаксиса: отступы или скобочки, как эти скобочки расставлять, int* p или int *p и прочие "чрезвычайно важные нюансы". С опытом понимаешь, что всё это ерунда. Только опыт желательно иметь разноплановый. Скажем, пощупать лисп с хаскелем, а не только на С десять лет писать.

                            +4
                            Не забывайте «пробелы или табы», главный философский вопрос ушедшего века!
                              0
                              First, Rust style is to indent with four spaces, not a tab.

                              Даже этот вопрос имеет решение в Rust.


                              https://doc.rust-lang.org/stable/book/ch01-02-hello-world.html

                                0

                                Да ну? hard_tabs = true. Пробелы по дефолту, но и всё. У нас например половина настроек форматирования отлична от дефолтов так-то.

                                0

                                Эта проблема, на самом деле, перестаёт выглядеть надуманной, когда открываешь код в редакторе с альтернативными настройками на количество пробелов. Типа какого-нибудь mcedit (а бывает, что приходится).
                                Табы плохой способ форматирования, хоть и терпимый.
                                Смешанный стиль — печаль...

                              0

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

                                0
                                В том то и дело, что чтобы чёткие претензии к синтаксису раста составить, нужен будет сеанс психоанализа. Я же написал, что это нечто иррацопнальное, не нравится и всё. Причём все идеи заложенные в раст очень нравятся, но смотреть на код неприятно.
                                Там вон уже и минусов понаставили, и в узости кругозора обвинили, а на самом деле это возможно из разряда фобий, или просто нечто социо-культурное. Вот как пример www.quora.com/Why-do-some-people-dislike-people-with-disabilities попытка объяснения. Но явление довольно массовое.
                          +7

                          А, ну теперь


                          понятно

                          почему на агде никто не пишет.


                          То есть, я потом это всё


                          отрефакторил

                          конечно, и всё стало красиво, но это другая история.

                            +1

                            Мне кажется, тут где-то уже спрашивали и вы что-то отвечали — но как эти символы вводить? Отдельную раскладку или IDE должна уметь в подстановку "tau" → "τ"?


                            UPD: нашёл, спасибо.

                              +1
                              Для непосвящённых это выглядит как-то так:

                            +9

                            По вашему синтаксис языка программирования не достоин обсуждения?

                              +31
                              По-моему, употребление термина «токсичность», не относящееся к токсикологии и смежным темам — это красный флаг в целом.

                              А предметное обсуждение особенностей языков программирования — это круто и очень полезно.
                                +23

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

                                  +1
                                  Красный флаг в том плане, что в прогрессивном 21 веке это слово вызывает аналогию с заменой ключевых слов в ЯП, чтобы чёрные не обижались (привет питон).
                                    +4
                                    А что за ключевые слова в питоне заменили?
                                      +3
                                        +3

                                        Так это же про документацию. Никто кейворды не трогал, к счастью.

                                          +2
                                          Mat1lda употребил словосочетание «ключевые слова» не в том значении, которое поняли вы. Определение из википедии: «Ключевые слова — особо важные, общепонятные, ёмкие и показательные для отдельно взятой культуры слова в тексте»
                                    0

                                    Абсолютно согласен.


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


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


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

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

                                      Пользователю языка — без разницы.
                                      Разработчикам новых языков — полезно учиться на чужих ошибках, чтобы их не повторять.
                                        0
                                        Разработчикам новых языков — полезно учиться на чужих ошибках, чтобы их не повторять.

                                        Это было бы круто, кабы существовала непротиворечивая корректная шкала оценки синтаксиса. А так, как ее нет, то и определить — ошибка ли это, или суперспособность — возможным не представляется. «Демократичным» голосованием это определять? — Увольте.


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


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

                                          +4
                                          Тем и полезны статьи вроде этой, что каждый может ознакомиться с аргументами всех сторон, и сделать выводы самостоятельно.
                                        +1

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

                                          +6
                                          Совершенно очевидно, что любой язык (да, и кобол, и сиквел — тоже) превращается из нечитаемого в читаемый по мере погружения.

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

                                            –2

                                            За что я люблю синтетические примеры, так это за всё.


                                            int (*fns[16])(int * const * p)

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


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

                                              +4
                                              Я готов поспорить, что когнитивные усилия здесь придется тратить на понимание того, как этот тип заполняется

                                              И это, и что этот тип из себя представляет. В том и проблема. Пример реальный, а не синтетический.

                                          +2
                                          А выражение «ядовитый тон» для вас тоже красным флагом является?
                                            +1
                                            Словарь гласит, что «ядовитый тон» означает «презрительный».
                                            Синтаксис ЯП может быть презрительным?
                                              –1

                                              Вполне себе синтаксис можно презирать.

                                                0
                                                Речь же явно идёт о оттенке цвета.
                                                  +1
                                                  Можно хоть один пример текста, где бы «ядовитый тон» означал оттенок цвета?
                                                    +2
                                                    Создатели Rage 2 поиздевались над Ubisoft
                                                    Все из-за внешнего сходства Rage 2 и грядущего Far Cry: New Dawn — оба шутера выполнены в ядовитых тонах и посвящены постапокалипсису.

                                                      +2
                                                      Любопытно: у сочетания «в ядовитых тонах» всегда цветовое значение, у сочетания «в ядовитом тоне» — всегда голосовое.
                                          –2
                                          ну я думаю под «токсичностью» подразумевалась плохая читабельность и автор хотел внести этим какой то литературной красоты, эх какое словцо приловчил к тексту! А вообще код на Расте и правда трудно читать, особенно новичкам, не знаю, возможно просто привык к Си стилю(и терпеть не могу Perl). Но есть люди, которые высказывают мнение о плохо читаемости, и думаю стоит им пояснить почему создатели Раста решили сделать именно так, а не иначе, чтобы переманить часть тех, кого это не устроило в языке.
                                            +5

                                            Вы саму статью читали? Хотя бы вводную ее часть?

                                            +3

                                            "Токсичность" можно вольно транслировать как "недружелюбность". И недружелюбность чего-бы-то-ни-было, синтаксиса или ещё чего вполне возможна.

                                            +6

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

                                              +4

                                              ВЖ пугают с непривычки. Когда приходит понимание, что их синтаксис не просто так аналогичен дженерикам, становится совсем просто.

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

                                                  Даже любопытно стало. А примера неочевидности под рукой не сохранилось?

                                                    +6

                                                    Ну есть более-менее очевидные например асинки. А есть менее очевидные, например:


                                                    trait SomeTrait {
                                                        fn foo(value: &i32) -> Self;
                                                    }
                                                    
                                                    struct Bar<'a>(&'a i32);
                                                    
                                                    impl<'a> SomeTrait for Bar<'a>
                                                    {
                                                        fn foo<'b>(value: &'b i32) -> Bar<'b> {
                                                            Bar(value)
                                                        }
                                                    }

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


                                                    Можно было бы попытаться сделать такую сигнатуру:


                                                    impl<'a> SomeTrait for Bar<'a>
                                                    {
                                                        fn foo<'b: 'a>(value: &'b i32) -> Bar<'b> {
                                                            Bar(value)
                                                        }
                                                    }

                                                    Но компилятор не разрешит, скажет что оригинальная сигнатура другая:


                                                    error[E0195]: lifetime parameters or bounds on method `foo` do not match the trait declaration
                                                     --> src/lib.rs:9:11
                                                      |
                                                    2 |     fn foo(value: &i32) -> Self;
                                                      |           - lifetimes in impl do not match this method in trait
                                                    ...
                                                    9 |     fn foo<'b: 'a>(value: &'b i32) -> Bar<'b> {
                                                      |           ^^^^^^^^ lifetimes do not match method in trait
                                                    
                                                    error: aborting due to previous error

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

                                                      +4

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


                                                      trait SomeTrait<'a> {// Обратите внимание на время жизни, добавленное в типаж
                                                          fn foo(value: &'a i32) -> Self;
                                                      }
                                                      
                                                      struct Bar<'a>(&'a i32);
                                                      
                                                      impl<'a> SomeTrait<'a> for Bar<'a>
                                                      {
                                                          fn foo(value: &'a i32) -> Bar<'a> {
                                                              Bar(value)
                                                          }
                                                      }

                                                      Еще один пример — стандартный типаж Index, возвращающий ссылку на свои внутренности. Из-за этого невозможно возвращать только что сконструированные структуры-обертки над частью своих внутренностей и это иногда зарубает интересные идеи API.

                                                        +4

                                                        Ну вот речь о том, что типаж нельзя поменять (в реальной задачи это actix_web::FromRequest), форкать или патчить либу ради этого как-то не очень хочется.


                                                        Так что проблемы с лайфтаймами бывают вполне себе. Казалось бы, зачем трейту знать про то что реализация хочет лайфтайм как-то именовать и что-то на основании этого делать? Да вроде незачем. А оказывается, что без этого нифига не работает.

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

                                              Может я что-то упустил, но Rust имеет ML-подобный синтаксис. Очень велико влияние OCaml, на котором он изначально и писался.
                                                +3
                                                Там от ML рожки да ножки остались. До версии 1.0 можно было говорить про ML-подобный синтаксис, потом началась игра в «поддавки» C-подобному синтаксису.
                                                  +3

                                                  Сравните синтаксис раста с синтаксисом какого-нибудь хаскеля.


                                                  Да даже какая-нибудь агда ближе к ML, имхо.

                                                  +1
                                                  К сожалению оказалось, что от него совсем не просто избавиться: конструкция A<B>() выглядела бы логичнее и лаконичнее, но в контексте выражения парсер не может отличить ее начало от операции сравнения A < B. Отсюда и возникает необходимость дополнить угловую скобку разделителем сегментов в пути ::.

                                                  А если не перегружать символы < и >, и вместо этого писать A[B]()?
                                                  Тогда и токсичных рыб нет, и метафора «из множества реализаций A берём соответствующую B» совершенно прозрачная.
                                                    +8

                                                    Квадратные скобки заняты для слайсов и массивов.

                                                      0
                                                      И в чём проблема? Имя функции не может быть ни массивом, ни слайсом.
                                                        0

                                                        Да, но синтаксически содержимое квадратных скобок в каждом случае разное.

                                                          +2
                                                          И в чём проблема? У круглых скобок в объявлении функции и у круглых скобок в арифметическом выражении тоже содержимое синтаксически разное.
                                                            +7

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


                                                            А проблемы возникают самые разные. Например, Rust обходится без предварительного объявления рекурсивных типов или функций (просто нет такой возможности, потому что не нужно), а в C++ их сначала нужно декларировать, а потом объявлять (кстати, это куда более глубокие вещи, чем лежащие на поверхности фигурные скобочки, которые тут обсуждают).

                                                              –1

                                                              И именно поэтому есть fn.

                                                                0

                                                                А в этом случае у парсера уже есть подсказка, что-есть-что в виде ключевого слова fn. И опять не нужно знать извне, что мы парсим — объявление функции или арифметическое выражение. Эта информация уже закодирована в разбираемом тексте. Если выкинуть fn, как тут выше уже предлагали, то это становится невозможно (не говоря уже о том, что эти люди вообще что ли не пытались никогда ничего искать по коду на GitHub.com?)

                                                              +2

                                                              То есть у вас А::<В>(), то есть турбофиш заменится на A[B](), окей.
                                                              А теперь представьте себе массив А, в котором лежат указатели на функции, и вы используете имя счетчика В. Тогда выглядеть это будет: А[В](). Упс…

                                                                +2
                                                                Так а в чём «Упс...»-то?
                                                                Вас ведь не смущает, что для поиска в HashMap по ключу используются те же самые квадратные скобки, но семантика у них совсем другая, чем при индексировании массива?

                                                                В верхнем комментарии треда я указал, что оба названных вами случая обобщаются как «из набора функций, обозначенного A, взять одну конкретную, соответствующую B», так что схожесть синтаксиса для этих двух случаев совершенно намеренная.
                                                                  0

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

                                                                    +2
                                                                    Осталось понять, почему разница между поиском во время выполнения и во время компиляции должна быть отражена в синтаксисе, а разница между сложением указателей и поиском в хеш-таблице — не должна быть отражена в синтаксисе.
                                                                      0
                                                                      а разница между сложением указателей и поиском в хеш-таблице — не должна быть отражена в синтаксисе.

                                                                      А почему должна? И то и то специально построено на абстракции типажа Index — поэтому использование и не должно отличаться.


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


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

                                                                        +1
                                                                        А почему должна?

                                                                        Я о том и говорю, что ни та ни другая не должна; что подстановка чего бы то ни было во время компиляции — деталь реализации, неинтересная пользователю языка.
                                                                        Rust может и значения элементов массива подставлять во время компиляции, несмотря на [].

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

                                                                        Я о том и говорю, что A[B]() — вызов «относительно произвольного» кода, хоть A массив, хоть хеш-таблица, хоть дженерик.
                                                                          0
                                                                          Rust может и значения элементов массива подставлять во время компиляции, несмотря на [].

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

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

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

                                                                      +1
                                                                      Можно пойти дальше и вообще все скобки, включая {}, заменить на круглые. Получится лисп :-)
                                                                        +1
                                                                        О, привет Матлаб.
                                                                        На деле же получается не очень удобно.
                                                                          +1

                                                                          Можете рассказать, какие с этим возникают проблемы?

                                                                            +2
                                                                            В принципе, никаких.
                                                                            Но я не люблю синтаксис, завязанный на чуткое понимание контекста. То есть, когда есть миллион использований одних и тех же символов и в зависимости от контекста у них разное значение.
                                                                            Хочется, чтобы контекст «берем N-ый элемент» вычитывался сразу, а не путался с «вызываем функцию от N».
                                                                            Поскольку редактор в самом Матлабе не совершенствуется уже кучу лет и подсветка и навигация по коду остаются достаточно примитивными, если хочется прочитать и вникнуть в кусок кода, то такие случаи с круглыми скобочками могут приводить к тому, что придется прыгать по всему скрипту.

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

                                                                              Банальный пример, который часто бесит.


                                                                              Есть функция f, принимающая число и возвращающая масив. Вам нужен второй элемент. В нормальном (читай, любом) языке вы напишите res = f(10)[2] (нумерация с единицы), но в Матлабе будет res = f(10)(2). На этом этапе парсер вам скажет "какие нахрен скобки за скобками? Я не понимаю". И приходится out = f(10); res = out(2), или [~, res] = f(10). Со вторым, однако, проблема, потому что количество аргументов в таком матчинге может влиять на алгоритм и вывод функции — то есть не всегда применимо, но это уже немножко другая история.


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


                                                                              Но самое паршивое, это когда читаешь код плохого прогера (а МАТЛАБ — система для инженеров). И пойди тогда разберись, что такое ar(i, k)— индексация массива или вызов функции.

                                                                                +5
                                                                                И парсер тут не тупой: функция ведь может возвращать функцию, и что тогда делать? В какой байт-код компилить?

                                                                                Нет, тут тупой либо парсер, либо весь матлаб.


                                                                                Во-первых, парсер в любом случае не занимается генерацией байт-кода (ну, в норме, по крайней мере). И унифицированный синтаксис облегчает работу парсеру, а не усложняет её (и не усложняет работу последующих стадий при наличии хоть какой-то статической проверки типов — им всё равно нужно эту самую проверку выполнять).
                                                                                Во-вторых, какая разница, писать out = f(10); res = out(2); или сразу f(10)(2)? От сохранения в переменную, у которой даже аннотации типа нет, тайпчекеру или кодгену больше информации доступно не станет. Или, иными словами, всё равно ведь надо как-то решать, вернула f функцию или массив, чтобы понять, что лежит в out, и в какой байт-код компилировать out(2).


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

                                                                                  0

                                                                                  Буквальный код ошибки выглядит так


                                                                                  Error: Indexing with parentheses '()' must appear as the last operation of a valid indexing expression.

                                                                                  Indexing with parentheses '()' к тому, что индексирование тут ещё бывает через фигурные скобки для специальных массивов.


                                                                                  Если столько не исправляют, то, скорее всего, что-то фундаментальное. В принципе, я согласен с вами в том, что причина, скорее всего, не в "функция ведь может возвращать функцию, и что тогда делать?".

                                                                                    +4

                                                                                    Скорее всего это фундаментально положенный болт.

                                                                                      0

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

                                                                                        +1

                                                                                        А я уверен, что именно в этом и дело. Это общая беда любых языков программирования, которые идут "в нагрузку" к другому программному продукту.


                                                                                        Расчёт тут простой: если у всего продукта целиком недостаточно фич — язык его не спасёт, а если достаточно — то пользователи никуда не денутся. Поэтому сам язык развивается по остаточному принципу, лишь бы на нём хотя бы в теории можно было писать. Это и есть фундаментально положенный болт.

                                                                      0

                                                                      A[B] это еще доступ по индексу.

                                                                        0
                                                                        См. выше
                                                                        +4

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

                                                                          +2
                                                                          В целом я согласен, но конкретно с ::<> я не вижу:
                                                                          • Какую информацию сейчас может извлечь IDE из ещё недописанного кода, пока функция слева от рыбы ещё не определена? Какая информация потерялась бы при переходе к []?
                                                                          • Как допущение имени типа внутри [] мешает подсветке синтаксиса?

                                                                          Проблема, которую я вижу — что если внутри [] будет тип вместо выражения или наоборот, то это отловится не сразу при парсинге, а на более позднем этапе. Но и здесь ничего нового: сейчас неподходящий индекс в let hello = [42]; print!("{}", hello["world"]); тоже ловится не сразу при парсинге, а на более позднем этапе.
                                                                            +4
                                                                            Какую информацию сейчас может извлечь IDE из ещё недописанного кода, пока функция слева от рыбы ещё не определена

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


                                                                            Написав x::< уже известно, что x или имя функции, или имя типа, а дописав x::<T>( уже понятно, что точно имя функции. Основываясь на этом IDE может искать x в своей базе данных функций или типов и предлагать, что делать дальше. Конечно, если такой функции/типа еще не написано, то она ничем не поможет, но если есть — то уже поможет.


                                                                            Если использовать синтаксис с квадратными скобками, то написав x[ непонятно, что такое x — имя функции? имя типа? слайс?


                                                                            Написав x[T]( это все еще неясно — x это слайс или имя функции.


                                                                            А если в области видимости окажется и слайс с именем xи имя функции или имя типа с именем x? Тут IDE предлагать адекватные варианты уже сложнее — если она сможет понять, что именно ожидается в месте написания кода, то предложит, а если нет — то в лучшем случае будут лишние варианты а в худшем — только неправильные.


                                                                            Проблема, которую я вижу — что если внутри [] будет тип вместо выражения или наоборот, то это отловится не сразу при парсинге, а на более позднем этапе.

                                                                            Есть еще проблема. В Rust можно инстанцировать Unit-структуры просто написав их имя:


                                                                            struct Unit;
                                                                            // x имеет тип Unit и содержит единственное значение Unit этого типа
                                                                            let x = Unit;

                                                                            x[Unit]

                                                                            Это индексация выражения x только что созданным экземпляром типа Unit или инстанцирование generic с типом Unit?


                                                                            Зависело бы от того, что такое x.


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


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

                                                                              +1
                                                                              Конечно, если такой функции/типа еще не написано, то она ничем не поможет, но если есть — то уже поможет. Если использовать синтаксис с квадратными скобками, то написав x[ непонятно, что такое x — имя функции? имя типа? слайс?

                                                                              Я о том и говорю, что пока x не определено — IDE не поможет ни при том ни при другом синтаксисе, а как только x определено — IDE известно, что это такое, и при том и при другом синтаксисе.

                                                                              А если в области видимости окажется и слайс с именем xи имя функции или имя типа с именем x?

                                                                              То это ошибка «E0428: the name is defined multiple times» хоть при том, хоть при другом синтаксисе.
                                                                                0
                                                                                Как это не поможет?! — x, не определено, но понятно, что это имя функции -> IDE может сама мне сгенерировать «рыбу» определения функции.
                                                                                0
                                                                                В Rust можно инстанцировать Unit-структуры просто написав их имя

                                                                                А зачем их как-то отдельно ещё и инстанцировать? Вообще сомнительная фича.

                                                                                  0

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

                                                                                    –1

                                                                                    Они сами себе инстансы же.

                                                                                      0

                                                                                      Нет, их нужно инстанциировать, если необходимо получить объект данного типа. Как и другие структурные типы.

                                                                                        0

                                                                                        Ну например Result<(), Error> (функции типа записи в файл) требуют в конце Ok(()) чтобы компилировалось. Без инстанса () типа () тут не обойтись

                                                                                          0
                                                                                          Вопрос в том, зачем создавать больше одного инстанса каждого такого типа.
                                                                                            +1

                                                                                            А как по-другому? Каждая функция возвращающая () должна создать свой экземпляр этого типа, иначе не работает. Это же не ГЦ язык где можно просто так ссылку вернуть, в расте ссылка должна была бы явно обозначаться &().


                                                                                            А ещё бывают массивы юнитов


                                                                                            let units: Vec<()> = xs.map(print).collect();
                                                                                            0

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

                                                                                              +2

                                                                                              Стандартный ничем не отличается от энума enum MyUnit { Unit }. Наделять его какими-то специальными свойствами "просто шобы було" нет никакого смысла — и так работает, зачем вкоряживать кастомную логику? Так-то кому-то массив юнитов может показаться бесполезным, вещь которую нужно запретить, но в дальнейшем оказывается что всё несколько сложнее.

                                                                                          0

                                                                                          Ну, не совсем, unit-like структуры чаще используются в качестве тИповых маркеров, а не явно в коде.

                                                                            +9
                                                                            Интересная тема.
                                                                            Во-первых, конечно, любому современному сложному языку программирования не хватает спецсимволов. Тех же скобок, например, чтобы не использовать знаки «больше» и «меньше» в качестве скобок. Да, есть целый Unicode, но нужно чтобы эти символы были на всех клавиатурах мира — а значит, мы опять возвращаемся к ASCII. Наверное, неплохим решением было бы, если бы консорциум Unicode выделил 20 наиболее важных символов и предложил бы наносить их на все клавиатуры на буквенные клавиши (возможно потребовался бы еще один Shift). Для полной совместимости с ASCII можно было бы даже
                                                                            продублировать их в начале ASCII, в кодах 0x01..0x1F (исключая POSIX-символы переносов строки, табуляций и чего-то там еще). Но такое чудо вряд ли случится:)

                                                                            Фигурные скобки и точка с запятой нужны. Это именно то, что позволяет структурировать код явно, не надеясь на невидимые пробелы и табы, как это принято в Python, а также make-файлах и еще каких-то древних форматах.

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

                                                                            Сокращения в ключевых словах это хорошо, особенно fn. Это лучше чем функции в C/C++ без ключевых слов вообще. Значительно упрощает работу парсера. А вот стрелки в сигнаруте функций… в Go вообще обошлись без спецсимволов там. Просто возвращаемый тип после скобок с аргументами.

                                                                            Замыкания — нет, не нравятся. Я бы вообще не стал делить функции на обычные и замыкания, использовал бы везде fn как универсальное ключевое слово. Кстати, в Rust вроде бы нет явного связывания переменных, как в С++ (в квадратных скобках).

                                                                            Также не нравится применение символа "|" в паттерн-матчинге. Это же всегда было «Битовое ИЛИ», зачем его мешать сюда? Чем запятая не угодила?
                                                                                match x {
                                                                                    1 | 2 => println!("one or two"), // ну и что это такое? "Битовое или"  или несколько аргументов матчинга?
                                                                                    _ => println!("anything"),
                                                                                }


                                                                            С использованием :: наверное авторы правы, хотя мне не нравится. Опять же — недостаточно спецсимволов!

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

                                                                            Для «цитирования» кода в макросах тоже не помешали бы особые, уникальные скобки-кавычки. Ну и т.д.
                                                                              –1

                                                                              Совместимость с ASCII в век победившего UTF-8 уж точно не нужна. Вот в раскладку клавиатуры новые скобки по-включать — идея по-лучше, жаль только нереализуема.

                                                                                0
                                                                                Ну есть в этом что-то логичное и правильное — «законодательно» разделить все символы на 3 группы: «международные», «национальные» и «дополнительные». И сделать так, чтобы «международные» кодировались 1 байтом даже в utf-8 (и заодно у производителей клавиатур появилась бы мотивация их наносить на клавиши), «национальные» укладывались в два байта utf-16 (кодовую плоскость, если я не ошибаюсь), а всякие эмодзи и древние алфавиты — всё остальное.
                                                                                А первые 32 байта ASCII как раз не используются, кроме нуля, пробела, переносов строк и табов. Совместимость с древними телетайпами вряд ли кому нужна.
                                                                                  +1
                                                                                  Вы из какого года пишете? Windows API до сих пор не поддерживает строки в UTF-8.
                                                                                    +4

                                                                                    Ну, Windows API вместо этого UTF-16 использует. Невелика разница.


                                                                                    Однако, код программы напрямую в Windows API никто передавать не будет, а все нормальные текстовые редакторы UTF-8 таки поддерживают.

                                                                                      +3
                                                                                      Про год лучше у вас спросить. Потому как в 2021м у большей части населения планеты Windows таки UTF-8 поддерживает. Только суровый Enterprise отстаёт.
                                                                                        +7

                                                                                        Уточнение: поддерживать-то поддерживает, но баг в conhost до сих пор не закрыли. ReadFile из стандартного ввода не может прочитать русские буквы из консоли.

                                                                                          0
                                                                                          Активная кодировка выбирается для всего процесса целиком, т.е. если в программе используется хоть одна сторонняя либа, то смена активной кодировки для процесса может её поломать. Много вы видели программ под Windows без использования сторонних либ? Т.е. поддержка «для галочки» появилась, но пользоваться ей на практике пока невозможно.
                                                                                      0
                                                                                      Под APL, где синтаксис состоял чуть менее, чем полностью из загадочных символов, делали специальные клавиатуры.

                                                                                      Если языки программирования массово будут использовать символы Юникода (как это уже делает Raku), то подтянутся и производители клавиатур.
                                                                                        +4
                                                                                        Не, не подтянутся. Программисты это слишком малая часть пользователей клавиатур. Боюсь, уже поздно, а вот предлагаемая мной модификация Unicode, будь она сделана сразу в 1991 году, может и помогла бы.
                                                                                        +3
                                                                                        Во-первых, конечно, любому современному сложному языку программирования не хватает спецсимволов.

                                                                                        Ох нет спасибо: я только сегодня возился с кодом на Xtend, в котором как часть синтаксиса используются ёлочки «». Часть исходников была в UTF-8, часть — в CP-1252, и используемая кодировка в самом файле никак не обозначена — «догадайся мол сама».
                                                                                          +3
                                                                                          и предложил бы наносить их на все клавиатуры на буквенные клавиши (возможно потребовался бы еще один Shift)

                                                                                          Что делать тем, у кого на клавишах уже нет места для новых значков, а вокруг них уже нет места для новых Shift?

                                                                                            +2
                                                                                            На большинстве клавиш по 3 символа. Вполне влезет и 4.
                                                                                            Но кстати, какие у них интересные есть символы — полускобочки двух видов, еще одни «панциреобразные» скобочки ❲ ❳
                                                                                            В принципе, такие бы всем непомешали, соответственно этим ребятам свободных клавиш понадобилось бы меньше:)
                                                                                              +2
                                                                                              На всякий случай поясню, что 「...『...』...」— это кавычки (для прямой речи, названий и т.д.): обычные и вложенные; а 〔...〕 используются для вставки авторского пояснения в прямую речь.
                                                                                                +4
                                                                                                Ну в программировании мы бы им обязательно нашли применение!
                                                                                                  +1
                                                                                                  Какая разумная пунктуация в языке!
                                                                                                  Я серьёзно.
                                                                                              +1

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

                                                                                                +4
                                                                                                Да, есть целый Unicode, но нужно чтобы эти символы были на всех клавиатурах мира

                                                                                                Не нужно. Например, чтобы набрать строку foo : Γ ⊢ SLam τ ε ⦂ τ₁ ⇒ τ₂ → ⊥, я пишу на клавиатуре foo : `GG `|- SLam `Gt `Ge `:<Down> `Gt`_1 `r= `Gt`_2 `to `bot.


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

                                                                                                был похож на код


                                                                                                Фигурные скобки и точка с запятой нужны. Это именно то, что позволяет структурировать код явно, не надеясь на невидимые пробелы и табы, как это принято в Python, а также make-файлах и еще каких-то древних форматах.

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

                                                                                                  +5
                                                                                                  Думаю, что и в хаскеле, и в идрисе, и в агде еще более своя атмосфера:)
                                                                                                    +2

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

                                                                                                      –1

                                                                                                      Все-таки Rust — это императивный язык, поэтому сравнение с Haskell в этом отношении немного неуместно. Например, в Haskell нет традиционного для императивных языков условия if без else, а в Rust оно есть. Ну и там, где возникает в Haskell необходимость писать императивно, появляются и фигурные скобки, и точка с запятой. Хоть опционально, но ведь почему-то они в языке присутствуют, не так ли?

                                                                                                        +4
                                                                                                        Например, в Haskell нет традиционного для императивных языков условия if без else

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


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

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


                                                                                                        let { a = 10; b = 20 } in a

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


                                                                                                        let a = 10
                                                                                                            b = 20
                                                                                                         in a

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


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


                                                                                                        foo = do
                                                                                                          a <- readLine
                                                                                                          b <- readLine
                                                                                                          when (a /= b) $ do -- этот ваш if
                                                                                                            putStrLn "yay"
                                                                                                            ...
                                                                                                    –1
                                                                                                    Нет, это визуальный мусор. Фигурные скобки и точки с запятой для структурирования не нужны ни в хаскеле, ни в идрисе, ни в агде, и у меня ни разу из-за этого не было проблем

                                                                                                    Мне напомнить, зачем в GHC ввели расширение EmptyCase?

                                                                                                      0

                                                                                                      Напомните. Очень интересно, как это связано со структурированием кода.

                                                                                                        –1

                                                                                                        Разбор значения типа-суммы без вариантов. В синтаксис на пробелах оно не впихивается ну вот вообще никак.

                                                                                                          +3

                                                                                                          Ага. Языка, где пишут так:


                                                                                                          foo : ⊥ → a
                                                                                                          foo ()

                                                                                                          не существует. И языка, где для указывания пустых случаев используется impossible, тоже не существует.


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

                                                                                                    +1
                                                                                                    любому современному сложному языку программирования не хватает спецсимволов.

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


                                                                                                    Тех же скобок, например, чтобы не использовать знаки «больше» и «меньше» в качестве скобок

                                                                                                    Мне в этом плане нравится, как это сделано в D. Зачем пытаться копировать неудачный синтаксис из C++ и усложнять парсинг, когда можно использовать любой другой символ: !, @, #, $, означающий, что дальше идёт набор шаблонных параметров:


                                                                                                    Dictionary![TKey, Tuple![Item1, Item2]] или Dictionary!(TKey, Tuple!(Item1, Item2))


                                                                                                    Также не нравится применение символа "|" в паттерн-матчинге. Это же всегда было «Битовое ИЛИ», зачем его мешать сюда? Чем запятая не угодила?

                                                                                                    А вот тут мне нравится, как это сделано в C# — через добавление новых ключевых слов, а не усложнение логики старых:


                                                                                                    x switch 
                                                                                                    {
                                                                                                      1 or 2 => ...,
                                                                                                      >3 and <6 => ...,
                                                                                                      _ => ...,
                                                                                                    }
                                                                                                      +2
                                                                                                      В D действительно правильно сделано. И в Rust этот подход кстати тоже применен — для атрибутов: символ # изменяет логику квадратных скобок
                                                                                                      #[test]
                                                                                                      fn test_foo() {
                                                                                                          /* ... */
                                                                                                      }

                                                                                                      А в C# сделано неправильно. Там есть оператор switch, его и нужно было расширять — разрешить ему возвращать значения, расширить синтаксис case-образцов и всё остальное, а они вместо этого взяли и ввели альтернативный синтаксис, совершенно ни на что не похожий. В конце концов, можно было бы кроме switch добавить еще match, в котором не требуется break после каждого case; но в остальном и switch и match должны обладать одинаковыми возможностями. Это логично, подобно тому как скажем циклы while и do..while существуют одновременно и отличаются только в одном аспекте.
                                                                                                    0
                                                                                                    возможно потребовался бы еще один Shift
                                                                                                    Эта проблема уже давно решена в странах, где пишут на польском и других странных языках с лишними буквами. Кстати, спецсимволов у них тоже там больше зачастую.
                                                                                                      +2

                                                                                                      Запятая будет лишним гемором для парсера: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=84d9b5daaacbb00406e433c389ecb869

                                                                                                      +2
                                                                                                      Однако на самом деле, предоставляемые Rust возможности требуют явного синтаксического разделения статических обращений xx::yy::zz и динамических xx.yy.zz, где результат будет зависеть от значения объектов xx, yy и zz во время выполнения.

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


                                                                                                      Программа, написанная на Rust, не имеет информации о типах во время выполнения, так как при компиляции происходит "стирание типов".

                                                                                                      Не вижу никакого "стирания типов" при компиляции. Автор явно понимает под этим словом что-то иное, не совпадающее со значением этого слова в Java или TypeScript.

                                                                                                        +3
                                                                                                        Разве в Rust можно получить тип переменной во время исполнения? Завязав логику на этом.
                                                                                                          +3

                                                                                                          При желании — да, см. трейт Any. При большем желании можно и что-то более хитрое придумать.


                                                                                                          Однако, я бы не сводил Type Erasure к возможности получить тип переменной во время исполнения. Ну или, если всё же принять ваше определение, всё равно остаётся непонятной следующая связка:


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

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

                                                                                                            +2
                                                                                                            Действительно, в такой реализации, как у Rust, можно добавлять к типам метки как общее поведение и обращаться с ними динамически, но ведь можно и не добавлять. =) Data.Typeable в Haskell похожим образом работает, но про него говорится «types erased». В Typescript аналог в принципе что-то вроде function f(s: {typeLabel: string}), если ввести в стандарные библиотеки.

                                                                                                            На самом деле мне удалось нагуглить вот какую проблему в параметричности Rust:
                                                                                                            In particular, the impl specialization RFC describes a language change that will allow the invocation of a polymorphic function f to end up in different sequences of code based solely on the concrete type of T, even when T has no trait bounds within its declaration in f.

                                                                                                            Код: play.rust-lang.org/?gist=461340df78e7db710b6e37b592c1ad6e&version=nightly
                                                                                                            Тут становится уже сложнее говорить про стирание типов.

                                                                                                            Но мне тоже непонятно, причем тут разделение на операторы `::` и `.`.
                                                                                                              +1
                                                                                                              При желании — да, см. трейт Any.

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


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

                                                                                                                +4

                                                                                                                А где говорилось, что должна быть какая-то информация о структуре типа? :-)


                                                                                                                В Java насколько я знаю статические методы резолвятся в рантайме, также как и не статические.

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


                                                                                                                Статик там означает, что вызов относится к классу, а не то, что вызов будет определен на этапе компиляции, как в Rust.

                                                                                                                Вы так пишете, как будто в Rust те вызовы, которые идут "через точку", не определены на этапе компиляции!

                                                                                                                  –2
                                                                                                                  А где говорилось, что должна быть какая-то информация о структуре типа?

                                                                                                                  Вот здесь говорилось, что этой информации нет во время выполнения: "Программа, написанная на Rust, не имеет информации о типах во время выполнения, так как при компиляции происходит "стирание типов". ". Мне показалось, что вы спорили с этим утверждением.


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


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

                                                                                                                    +2

                                                                                                                    А сколько информации требуется? Я вот считаю, что TypeId — это уже информация о типах. Вам зачем-то нужна информация о структуре. Кому-то ещё понадобится исходный код типа. В каждом случае получится разное понимание стирания типов. Где будем останавливаться и почему?


                                                                                                                    Java-код компилируется в инструкции виртуальной машины и речь именно об этой компиляции.

                                                                                                                    Но ведь если выкинуть детали реализации, то в таком случае всё ещё проще же: вызов статического метода приводит к инструкции invokestatic, после которой указывается вызываемый метод. Метод указывается компилятором статически. Где тут резолвинг в рантайме? :-)


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

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


                                                                                                                    Поэтому структуры в Rust не могут иметь статических полей, в отличии от классов Java, и статические методы в Rust — это просто функции, расположенные в пространстве имен структуры, ничего более.

                                                                                                                    Не вижу отличия от Java.

                                                                                                                      –1
                                                                                                                      Не вижу отличия от Java.

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

                                                                                                                        0

                                                                                                                        А какое отношение значения полей имеют к метаинформации?

                                                                                                                          0

                                                                                                                          Отредактировал свое сообщение.

                                                                                                                            0

                                                                                                                            А какое отношение значения статических полей имеют к информации о типе?

                                                                                                          –2

                                                                                                          Про скобки и отступы:
                                                                                                          Из-за древнего примера из си предлагается ещё последующие 100 лет дуть на воду. Опять же не надо забывать, что в современности мало кто программирует в notepad/nano, а отступ в vscode будет виден очень хорошо, за все годы я вспомнил что сделал такую ошибку только один раз, и то всё было найдено и исправлено меньше чем за минуту.


                                                                                                          Про блоки: опять же блоки нет никакой нужды обозначать именно скобками, от этого они не перестанут быть блоками в других языках. А вот про область видимости — тут это растёт больше из проблем borrow-checker'а, и в примере с локом, в других языках это выглядит менее вербозно и без борров-чекера, что-то типа:
                                                                                                          withLock(mutex): ...


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


                                                                                                          Читая про постфиксную запись, я скорее склонен считать, что это попытка выдать желаемое за действительное, можно написать точно такой же текст о том почему это в плюсах удобнее, например auto a = fb() сразу сообщает пользователю об автовыведение типа — удобно как бы. Т.е. не то что это действительно так, но довольно спорный вопрос как удобнее на самом деле.


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


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


                                                                                                          PS: всех с Новым Годом!

                                                                                                            +5
                                                                                                            Опять же не надо забывать, что в современности мало кто программирует в notepad/nano, а отступ в vscode будет виден очень хорошо

                                                                                                            Как насчет code review через веб-интерфейс? Или публикации кусков кода в мессенджерах? Или копи-пасты со stackoverflow? Отступ-то вы увидите, но у вас не будет дополнительного контроля, что вы не сдвинули что-то где-то. На Rust также распространена практика, когда тебе нужно что-то быстро проверить на работоспособность в онлайн-плейграунде и копируешь туда код зачастую с "поехавшими" отступами, но благодаря скобкам об этом не нужно беспокоиться.


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


                                                                                                            в других языках это выглядит менее вербозно и без борров-чекера, что-то типа: withLock(mutex): ...

                                                                                                            А если нужно лочить не одно значение, а сразу два или три? Городить вложенные блоки? И ведь речь не только о локах мьютекса, а вообще о всем, для чего удобно использовать RAII.


                                                                                                            Опять же нет никаких проблем

                                                                                                            fn foo() { .. }
                                                                                                            
                                                                                                            fn bar() -> bool { .. }
                                                                                                            
                                                                                                            let val = if x < 42 {
                                                                                                                foo(x)
                                                                                                            } else {
                                                                                                                bar(x)
                                                                                                            }

                                                                                                            Какого типа должно быть значение val?


                                                                                                            Опять же let mut избыточен, достаточно mut

                                                                                                            Нет никакого let mut, есть let PATTERN. let — это не оператор объявления переменных, это оператор сопоставления (в паттерне переменных может и не быть).

                                                                                                              +4
                                                                                                              А если нужно лочить не одно значение, а сразу два или три? Городить вложенные блоки? И ведь речь не только о локах мьютекса, а вообще о всем, для чего удобно использовать RAII.

                                                                                                              Пример прямиком из MSDN:

                                                                                                              using (StringReader left = new StringReader(numbers),
                                                                                                                  right = new StringReader(letters))
                                                                                                              {
                                                                                                                  ...
                                                                                                              }
                                                                                                              
                                                                                                                –3

                                                                                                                Ужас. Лучше бы в языке этого варианта не было!

                                                                                                                  0

                                                                                                                  withLock(mutex1, mutex2)

                                                                                                                    +8
                                                                                                                    Идея хорошая, но шаг влево и уже не работает:
                                                                                                                    using (StringReader reader = new StringReader(numbers),
                                                                                                                        writer = new StringWriter(...))
                                                                                                                    {
                                                                                                                        ...
                                                                                                                    }
                                                                                                                    

                                                                                                                    Через запятую поддерживается определение переменных только одного типа, а в реальной жизни обычно используется что-то вроде
                                                                                                                    using (var reader = new StringReader(numbers))
                                                                                                                    using (var writer = new StringWriter(...))
                                                                                                                    {
                                                                                                                        ...
                                                                                                                    }
                                                                                                                    

                                                                                                                    Мне и так нравится, но возможно кому-то это покажется уродливым.
                                                                                                                      0

                                                                                                                      Не знаю как в C#, но в Java с try-with-resources ответственность за корректное освобождение ресурсов перекладывается на вызывающую сторону. К тому же не всегда использование ресурсов настолько локализовано, что безошибочное использование try-with-resources очевидно.


                                                                                                                      Использование Cleaner улучшает ситуацию и избавляет от необходимости следить за освобождением в тривиальных случаях, но в более сложных — головной боли не избежать. О проблемах освобождения ресурсов в Java подробно рассказывается в докладе Евгения Козлова "Вы все еще используете finalize()? Тогда мы идем к вам".

                                                                                                                      +3
                                                                                                                      люди с плохим зрением их не видят, в отличии от скобок.

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

                                                                                                                        +1
                                                                                                                        Или копи-пасты со stackoverflow?

                                                                                                                        Перепечатайте, чай, руки не отвалятся.


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


                                                                                                                        Как насчет code review через веб-интерфейс? Или публикации кусков кода в мессенджерах?

                                                                                                                        А вот этого просто не понял. В чём там проблема?

                                                                                                                          0
                                                                                                                          Как насчет code review через веб-интерфейс?

                                                                                                                          Меня прежде всего интересует как оно в vscode, всё остальное вторично, если очень надо — надо в web-интерфейс запилить что-то типа плагина из vscode. Зачем дополнительно страдать ради тех кто копирует в чатах не могу понять, хотя большинство чатов уже нормально понимают


                                                                                                                          2+2

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

                                                                                                                          Не видно отступы, при этом видно {}? странно, ну да ладно, так как я, человек с плохим зрением, не хочу их расставлять для кого-то кроме себя, а расставлять я их не хочу, хотя когда-то в плюсах я считал это обязательным.


                                                                                                                          А если нужно лочить не одно значение, а сразу два или три

                                                                                                                          withLock(mutex1, mutex2):
                                                                                                                              ...

                                                                                                                          Какого типа должно быть значение val?

                                                                                                                          это невалидное выражение при данных сигнатурах, валидное должно быть drop(bar(x)), и это гораздо большая проблема если язык вообще позволяет писать такое, раст и не позволяет, но если считать что; это drop, то зачем мне делать drop на каждый вызов того что и так возвращает ()?


                                                                                                                          Нет никакого let mut, есть let PATTERN

                                                                                                                          я не очень понимаю как mut может относиться к части pattern

                                                                                                                            +5
                                                                                                                            я не очень понимаю как mut может относиться к части pattern

                                                                                                                            let (a, mut b) = function_returning_pair(arg);

                                                                                                                            Тут возвращаемое значение деконструируется на a и b, но только одно имя позволяет значение менять.

                                                                                                                              0

                                                                                                                              Ок. Признаться за всё время с растом ни разу не делал так. Однако всё равно не готов это сходу отнести маркер мутабельности к паттерн матчингу

                                                                                                                                +1

                                                                                                                                Это уже конструирование после сопоставления паттерна.
                                                                                                                                Этот mut никак на паттерн не влияет и не является его частью, так же как &, ref, ref mut или биндинг значения к другому имени.


                                                                                                                                С таким же успехом можно было бы это заменить на


                                                                                                                                let (a, b) = function_returning_pair(arg);
                                                                                                                                let mut b = b;

                                                                                                                                с точки зрения паттерна — это одно и то же.


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

                                                                                                                                0
                                                                                                                                но если считать что; это drop, то зачем мне делать drop на каждый вызов того что и так возвращает ()?

                                                                                                                                Можно и не делать. Но ; помимо этого превращает выражение в инструкцию, то есть выступает разделителем инструкций.

                                                                                                                                  +1

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

                                                                                                                                    0

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

                                                                                                                                      –1

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

                                                                                                                                        +1

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

                                                                                                                            +2
                                                                                                                            Такой подход избавляет от необходимости декларировать тип до того, как будет объявлена переменная этого типа. В C/C++ из-за этого приходится отдельно делать объявления, отдельно определения, и иногда изменение порядка деклараций может изменить семантику.

                                                                                                                            C# же как-то умеет без деклараций определять переменные и их типы, как в сишном стиле
                                                                                                                            int a;

                                                                                                                            так и в похожем как у rust, javascript и тд
                                                                                                                            var a = 1;


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

                                                                                                                            Так в чём проблема была сделать что-то типа такого:
                                                                                                                            let myFunc: (x: i32) -> bool;


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

                                                                                                                            Оффтоп: Всех с наступающим!
                                                                                                                              +8
                                                                                                                              C# же как-то умеет без деклараций определять переменные и их типы, как ...
                                                                                                                              Что же хорошего в нескольких способах записи одного и того же? В командах наоборот ограничивают такую креативность проверками стиля кода.
                                                                                                                              хотели сделать что-то типа околофункционального ЯП
                                                                                                                              Хотели сделать более последовательный и безопасный типизированный императивный ЯП без устаревшего багажа и сделали.
                                                                                                                                0
                                                                                                                                Очевидно, что зависит от использования.
                                                                                                                                Если мне нужно создать переменную типа int в шарпе — нет никакого смысла писать
                                                                                                                                var a = new int();

                                                                                                                                потому что гораздо читабельнее будет
                                                                                                                                int a;

                                                                                                                                Однако если создаётся какой нибудь

                                                                                                                                ObservableCollection<LongGenericTypeName> collection = new ObservableCollection<LongGenericTypeName>() ;

                                                                                                                                гораздо удобнее и читабельнее написать
                                                                                                                                var collection = new ObservableCollection<LongGenericTypeName>() ;
                                                                                                                                  +4
                                                                                                                                  потому что гораздо читабельнее будет int a;

                                                                                                                                  Не читабельнее, чем если было бы
                                                                                                                                  var a: int;

                                                                                                                                  Зато есть единственный синтаксис объявления переменной. Сейчас в С-подобных ЯП по сути просто развитие старого невыводящего типы анализатора, с сохранением старого синтаксиса. Поддержка int a; не есть сознательный «сахар» для удобства разработчиков, это на 100% легаси. Посмотрите как делается новый ЯП:
                                                                                                                                  1) На базе Java developer.android.com/kotlin/learn#variables
                                                                                                                                  2) После C# то же самое www.tutorialspoint.com/fsharp/fsharp_variables.htm
                                                                                                                                  Разработчики других новых ЯП принимают точно такие же решение, как и Rust. Потому что добавлять новый синтаксис, чтобы писать на 4 символа меньше — это добровольный отстрел ноги для будущего ЯП.
                                                                                                                                +1
                                                                                                                                let myFunc: (x: i32) -> bool;

                                                                                                                                Ну в статье же приведен пример, когда это вызывает проблемы:


                                                                                                                                let myFunc: (i32, i32);

                                                                                                                                Что это, функция или кортеж?

                                                                                                                                  +1
                                                                                                                                  Это кортеж
                                                                                                                                  А вот это функция:
                                                                                                                                  let myFunc: (i32, i32) -> ();

                                                                                                                                  И нет неоднозначности, просто взять приоритет у оператора ->, и уже по контексту понятно, что это аргументы функции, а не кортеж.
                                                                                                                                    –1

                                                                                                                                    Насколько я понимаю, язык помимо прочего стараются делать максимально контекстно-свободным, в том плане что по-возможности каждая конструкция имеет уникальный префикс и мы заране знаем что дальше. То есть не надо откатываться назад и начинать разбор повторно. В вашем случае мы в позиции let myFunc: (i32, i32) не знаем, есть ли дальше -> или нет, и в зависимости от этого трактуем или как функцию, или как кортеж. В языке стараются таких ситуаций не допускать, поэтому например по префиксу fn даже дальше парсить не надо: знаем, что там функция.

                                                                                                                                      +3
                                                                                                                                      То, что вы описали, называется LL(1); контекстно-свободный парсинг — это намного более широкий класс.
                                                                                                                                        0

                                                                                                                                        Верно, спасибо за более точную формулировку

                                                                                                                                        +1

                                                                                                                                        Контекстная свобода — это независимость интерпретации от контекста. Пример контекста — число открытых скобочек. То же, о чём вы говорите, к контекстам не имеет отношения. Разные конструкции с общим префиксом — типичная ситуация. Например: const и continue.

                                                                                                                                          –1

                                                                                                                                          Это одна и та же конструкция: в одном случае это будет Keyword("const"), а в другом — Keyword("continue"). Как будто вы никогда компиляторы не писали...

                                                                                                                                            +1

                                                                                                                                            С тем же успехом можно утверждать, что let myFunc: (i32, i32) парсится в


                                                                                                                                            [
                                                                                                                                                Keyword("let"),
                                                                                                                                                Name("myFunc"),
                                                                                                                                                Symbol(":"),
                                                                                                                                                Symbol("("),
                                                                                                                                                Name("i32"),
                                                                                                                                                Name("i32"),
                                                                                                                                                Symbol(")"),
                                                                                                                                            ]

                                                                                                                                            Подобный парсер даже для подсветки синтаксиса не годится.

                                                                                                                                              0
                                                                                                                                              Именно в такой список и лексится :)
                                                                                                                                              Парсеры в ЯП уже полвека как двухуровневые.
                                                                                                                                                0

                                                                                                                                                Ага, конечно:


                                                                                                                                                self.let = { continue: async ()=> let }
                                                                                                                                                let async = `${ let.continue( `{continue}` ) }`
                                                                                                                                        +1

                                                                                                                                        А указатель на функцию тогда как? Сейчас там тоже fn, только как часть типа.

                                                                                                                                          +2

                                                                                                                                          По моему


                                                                                                                                          fn (i32, i32)

                                                                                                                                          Выглядит чище, чем


                                                                                                                                          (i32, i32) -> ()

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

                                                                                                                                            +1

                                                                                                                                            По мне вторая запись информативнее: отображение кортежей из двух целых на единственное значение. Ещё бы встроенную возможность распаковывать кортежи для передачи параметров в функции, например, как в питоне: myfun(*(1, 2))


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

                                                                                                                                            Дык, ваша функция таки возвращает значение — юнит. Из функций, не возвращающих значение, нет возврата вообще (бесконечный цикл или паника, например), и запись fn (i32, i32) в равной степени может означать и то, что тип возврата — bottom.

                                                                                                                                              +2
                                                                                                                                              отображение кортежей из двух целых на единственное значение

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


                                                                                                                                              Функции, "возвращающие bottom", точнее не возвращающие управление — это такая редкость, что написать -> ! по такому случаю не сложно.

                                                                                                                                        +1
                                                                                                                                        Так в чём проблема была сделать что-то типа такого:
                                                                                                                                        let myFunc: (x: i32) -> bool;

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


                                                                                                                                        то есть


                                                                                                                                        let x = 5;
                                                                                                                                        let lambda = if 1 > 2 { || 10 } else { || x };

                                                                                                                                        error[E0308]: `if` and `else` have incompatible types
                                                                                                                                         --> src/lib.rs:3:44
                                                                                                                                          |
                                                                                                                                        3 |     let lambda = if 1 > 2 { || 10 } else { || x };
                                                                                                                                          |                             -----          ^^^^ expected closure, found a different closure
                                                                                                                                          |                             |
                                                                                                                                          |                             expected because of this
                                                                                                                                          |
                                                                                                                                          = note: expected type `[closure@src/lib.rs:3:29: 3:34]`
                                                                                                                                                  found closure `[closure@src/lib.rs:3:44: 3:48]`

                                                                                                                                        Так что отдельный синтаксис для фунок нужен в любом случае


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

                                                                                                                                        Полагаю это ложное ощущение. По крайней мере после шарпа и тайпскрипта раст воспринимается на ура.

                                                                                                                                          0
                                                                                                                                          В том что у лямбд в расте уникальный тип, который реализует только общий интерфейс «функция с сигнатурой i32 -> bool».

                                                                                                                                          И что в этом хорошего?
                                                                                                                                            +7

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

                                                                                                                                              +3

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


                                                                                                                                              let adder = |x: u32| x + 10;
                                                                                                                                              println!("{}", std::mem::size_of_val(&adder)); // печатает `0`

                                                                                                                                              Лямбда-функции в C++, если не ошибаюсь, работают похожим образом.

                                                                                                                                              +2

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

                                                                                                                                                +1

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


                                                                                                                                                let func = if 1 > 2 { || 10 } else { || 5 };
                                                                                                                                                // тот же размер, что и у указателя
                                                                                                                                                assert_eq!(
                                                                                                                                                    std::mem::size_of_val(&