Pull to refresh

Comments 76

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

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

Это Вы С++ во всей его красе наверное не видели

Я настолько привык к C++, что жить без него не могу и уже не замечаю проблемы с синтаксисом )
Спасибо за советы. Пойду смотреть Rust

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

Отторжения нет. Мне уже нравится ^_^

А действительно, расскажите пожалуйста для статистики, какие именно места синтаксиса кажутся вам наиболее срашными? Постараемся разобраться вместе.

Мне тоже вначале было непривычно и местами неприятно, потом привык и перестал обращать внимание. Лично мне не нравится всеобщий сокращизм и стремление писать ключевые слова и идентификаторы в 2-3 символа. Другие наоборот фанатеют от этого.

Вообще, после того, как наешься радости production style C/C++… когда устанешь разгребать тонны кода от именитых компаний и брендов, который на поверку оказывается банальным индийским *, то начинаешь ценить в первую очередь не синтаксис, не абстрактную выразительность или там лаконичность языка, а то, какие проблемы он решает и какие гарантии он дает.

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

Конечно, есть unsafe блоки, которыми можно устроить ад и в Rust, но их можно очень быстро отсмотреть. Можно даже настроить систему так, чтобы геррит/гитхаб разрешал ревьюить код, содержащий unsafe блоки только сениорам, тогда как safe Rust можно отдавать на ревью кому угодно.

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

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


Навскидку:


name: &'static str,

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


&self, f: &mut Formatter

но это просто нужно один раз понять (я не разбирался что всё это значит ещё. Догадываюсь, что, возможно, в Rust-е всё const-by-default, поэтому &mut. А & — потому что, так же, как и в плюсах, есть возможность передавать по ссылке/по значению. Но опять же — это просто догадки).


Непривычно смотрится просто знак восклицания println! и всякие штуки, типа slice: &[i32].
Дальше:


// A tuple struct
struct Pair(i32, f32);

только с комментария догадываюсь, что это аналог алиаса (?): using Pair = std::tuple<i32, f32>; и, возможно, доступ будет происходить по индексам.


Ну, а этот код из статьи — это вообще ад:


macro_rules! __impl_slice_eq1 {
    ($Lhs: ty, $Rhs: ty) => {
        __impl_slice_eq1! { $Lhs, $Rhs, Sized }
    };

    ($Lhs: ty, $Rhs: ty, $Bound: ident) => {
        #[stable(feature = "rust1", since = "1.0.0")]
        impl<'a, 'b, A: $Bound, B> PartialEq<$Rhs> for $Lhs where A: PartialEq<B> {
            #[inline]
            fn eq(&self, other: &$Rhs) -> bool { self[..] == other[..] }
            #[inline]
            fn ne(&self, other: &$Rhs) -> bool { self[..] != other[..] }
        }
    }
}

Опять же — уверен, что если разобраться — всё становиться логичным.
Спасибо

Очень многое вы угадали верно :) Давайте по порядку.
этот маленький ' крючёчек для меня, как человека с плохим зрением — просто сложно заметен.
Непривычно смотрится:
Идентификаторы с, как вы его называете крючком, это лайфтаймы. В отличие от С++, где понятия времени жизни объекта и отношения владения не выражены в коде явно, в Rust это элемент синтаксиса.

Когда в C++ вы принимаете аргументом конструктора и сохраняете в поле некую ссылку, вы неявно говорите «все взорвется, если конструируемый объект переживет тот, на который ссылается». Вам остается только надеяться что другие программисты не дураки и не передадут вам ссылку на временный объект.

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

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

fn my_function<'a, T>(reference: &'a T) -> &'a T;


Если вкратце — по умолчанию все параметры передаются по значению без копирования. То есть если вы передали что-то в другую функцию без &, больше вы его использовать не сможете. Это называется передача владения объектом. Передача же по ссылке производится явно, амперсандом (&). При этом владения объектом не происходит, и после возврата можно пользоваться им как обычно. Но одновременно можно заимствовать только неизменяемые ссылки, на которых нет в это же время изменяемой ссылки.
Как вы верно заметили, по умолчанию все ссылки и объекты неизменяемые. Изменяем ость нужно явно прописывать словом mut.
Знак восклицания после имени функции говорит о том, что это макрос. Вы ими пока голову не забывайте, есть темы поважнее.

Спасибо.


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

По сути, в плюсах — это аналог rvalue с мув семантикой. Но в плюсах нет поддержки овнершипа на уровне языка/компилятора:


std::string str = "I'm the owner";
foo(std::move(str)); // передали овнершип в функцию

Как я уже писал Halt — смотрю Rust из-за этих манипуляций с лайфтаймом :)

При работе с вж есть несколько первоочередных правил:
1. Если у вас ссылочный только аргумент функции — вж можно не ставить, оно по умолчанию будет считаться из той области видимости, где функция вызывается. Касается как self, так и просто параметров.
fn do(arg: &Type) { ... }

2. Если у вас ссылочный только результат функции с self — вж тоже можно не писать, оно привязывается к вж self (кроме случаев, когда вж результата зависит от другого параметра, и вам надо это явно указать)
fn do(&self) -> &T { &self.member_of_type_t } //например

3. Ясное дело, что ссылку на созданный внутри функции объект возвращать нельзя — dangling pointer. Для такого есть Box — ссылка на объект в куче.
fn make_stack() -> &T { ... } //EPIC FAIL
fn make_heap() -> Box<T> { ... } //EPIC WIN

4. Не создавайте функции с нессылочным self без крайней необходимости — они уничтожают self (из правила, написанного мной выше)
fn kill_self(self) { ... }

5. Если у вас член структуры — ссылка, вы обязаны ее вж таскать с этой структурой везде. Логично, потому что если ссылающийся объект помрет без нашего ведома, мы получим segfault.
struct Container<'a> {
memb: &'a Item
}
Если у вас ссылочный только аргумент функции — вж можно не ставить

Я бы даже сказал иначе: не стоит бросаться лайфтамы прописывать пока компилятор не заставит. (:

только с комментария догадываюсь, что это аналог алиаса (?): using Pair = std::tuple<i32, f32>; и, возможно, доступ будет происходить по индексам.

Вот тут не угадали :) Кортежи в Rust есть и они пишутся просто скобками:
// Функция, возвращающая кортеж
fn get_tuple() -> (i32, f32) {
    (42, 3.14)
}

// Деструктурирующее связывание:
let (x, y) = get_tuple();

// Можно и по индексам:
let t = get_tuple();
let x = get_tuple().0;
let x = t.1;


Структуры вида struct Pair(i32, f32); служат для другой цели. Их используют когда хочется связать несколько значений под одним общим именем типа, но отдельные поля именовать не хочется. Сюда можно отнести координаты, группы однотипных значений и т. д.

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

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

Такие конструкции можно сравнить с шаблонной магией в C++ которая очень мощная, но может быть довольно трудночитаема (загляните в boost или в stdlib).

Основная идея в том, чтобы упаковать весь ад внутрь абстракции, а наружу выставить вполне милый интерфейс, типа vec![], который скрывает всю сложность, но дает удобство простому пользователю.

Спасибо за объяснения.


Структуры вида struct Pair(i32, f32); служат для другой цели. Их используют когда хочется связать несколько значений под одним общим именем типа, но отдельные поля именовать не хочется.

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


struct Meters(f32) — подтверждает это. На псевдо коде мы имеем что-то такого:


void foo(f32); // 1
void foo(Meters); // 2

foo(f32{0}); // 1
foo(Meters{0}); // 2

(Подправте, если ошибаюсь)


С макросами — понял — аналог шаблонов. Но тогда


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

есть и макросы (которые судя по комментариям ниже могут генерить код/модули) и есть шаблоны.


Спасибо за объяснения. Я фан явного указания жизни обьектов. Из-за вас теперь уже точно смотрю на Rust :)

Грубо: макросы — кодогенерация, причём гигиеничная. Шаблоны — обобщённое программирование.
Добавлю: макросы Rust работают с синтаксическим деревом, а не с текстом, что исключает сишную define-анархию в принципе. Есть немногочисленные недочеты в том, как это работает сейчас (да, concat_idents!, я смотрю на тебя), но главный постулат языка — безопасность — нигде не нарушается.
Дженерики (шаблоны) в Rust — это реализация параметрического полиморфизма. Смысл у них примерно тот же что и у шаблонов C++.

Говорить что «макросы — аналог шаблонов» довольно странно, все равно как в контексте C++ говорить что, «#define — это аналог template».

Либо я вас не понял.
UFO just landed and posted this here
Ну, а этот код из статьи — это вообще ад:

Сорри.
(Это я писал.)

Конечно, есть unsafe блоки, которыми можно устроить ад и в Rust, но их можно очень быстро отсмотреть. Можно даже настроить систему так, чтобы геррит/гитхаб разрешал ревьюить код, содержащий unsafe блоки только сениорам, тогда как safe Rust можно отдавать на ревью кому угодно.

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

Строго говоря, наличие unsafe в рамках модуля уже требует аккуратного review всего кода в модуле, в том числе safe, т. к. он тоже может нарушить инварианты, на которые полагается unsafe часть. Пример, вроде, был в Nomicon'е.

Спасибо за комментарий! А вы не можете дать ссылку на этот момент? Я, к сожалению, такого не припомню.
Действительно, очень заковыристая ситуация. То есть rule of thumb такой: любой модуль, содержащий unsafe блоки должен считаться unsafe. А вот граница модулей таки обеспечивает необходимый уровень изоляции.

Ну и вот это надо запомнить:
Unsafe code must trust some Safe code, but can't trust generic Safe code. It can't trust an arbitrary implementor of a trait or any function that was passed to it to be well-behaved in a way that safe code doesn't care about.

Меня, например, пугает в первую очередь какая-то многословность синтаксиса в целом. При этом я понимаю, что явное лучше неявного и все такое, но некоторые куски вызывают ступор просто из-за обилия спецсимволов:
fn escape_str(wr: &mut fmt::Write, v: &str) -> EncodeResult 

fn tcx<'a>(&'a self) -> TyCtxt<'a, 'gcx, 'tcx>;


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

Кстати, об угловых скобках. Относительно частая необходимость вложенных шаблонных параметров вызывает чисто читательский дискомфорт, ведь угловые скобки — на самом деле не скобки, большинство IDE не подсвечивает их правильно и чисто визуально они воспринимаются хуже, чем круглые или квадратные (а я-то гадал, почему в Scala дженерики используют квадратные скобки):
fn ast_ty_to_ty_cache(&self) -> &RefCell<NodeMap<Ty<'tcx>>>;

Имхо, не стоило из С++ брать угловые скобки для шаблонов и :: для неймспейсов, все равно ведь остальной синтаксис не очень похож, да и способ мышления все равно совсем другой нужен.
Но тут, видимо, уже ничего не поделаешь.
Использование угловых скобок как для лайфтаймов, так и для «шаблонных» параметров имеет вполне конкретный смысл: и то и другое это проявление параметрического полиморфизма. В C++ код может быть generic over type, в Rust к добавок к этому он может быть generic over lifetime. Поэтому синтаксис здесь общий.

Синтаксис «имя: тип» я думаю выбран потому, что в отличие от C++ где аннотации типа обязательны, в Rust они являются скорее исключением и обязательны только в прототипах функций и структурах. В подавляющем большинстве случаев, типы внутри метода могут быть выведены полностью.

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

let x = 42;
let y: String = "hello_world".to_owned();

Делать же два разных синтаксиса объявления с аннотацией типа и без оной — еще хуже. Если делать по аналогии с C/C++

let x = 42;
String y = "hello_world".to_owned();

…то существенно усложняется парсинг, теряется читаемость и опять-таки появляется неоднозначность без особых преимуществ.
Синтаксис «имя: тип» я думаю выбран потому, что в отличие от C++ где аннотации типа обязательны, в Rust они являются скорее исключением и обязательны только в прототипах функций и структурах. В подавляющем большинстве случаев, типы внутри метода могут быть выведены полностью.

К этому моменту у меня претензий нет, когда изначально есть вывод типов через let (а не прикостыленное auto), это выглядит вполне органично.
А возвращаемое значение у функции через -> позволяет избавиться от безумного сишного синтаксиса указателей на функции. Это все понятно, просто непривычно.

Но вот угловые скобки вообще — это все-таки не очень.
Сравните:
Box<Map<Vec<i8>>>
Box[Map[Vec[i8]]]
Box(Map(Vec(i8)))


Для угловых скобок еще и шифт надо нажимать.
В C++ символ * в зависимости от контекста может означать:

  • оператор умножения: 2 * 3
  • указатель: char*
  • разадресацию указателя: *pint = 42
  • пользовательский operator *
  • наверняка, я еще что-то забыл, какие-нибудь триграфы

Компилятору C++ уже приходится разгребать завалы звездочек и применять шаблоны только чтобы построить AST.

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

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

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

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

Легко рассуждать об отдельном элементе синтаксиса, забывая обо всей остальной кухне.
В Scala поэтому круглые скобки используются в том числе для доступа к массивам. А в Lisp как-то обошлись только круглыми скобками.
Но я согласен, моей квалификации недостаточно, чтобы предложить более удачный вариант с учетом всего синтаксиса в целом.
[mode=«zanuda»]В скалке a(...) это сокращенный вызов метода a.apply(...). Работает для любого объекта с этим методом. [/mode]

плюс ещё update: a(1) = "xxx" эквивалентно a.update(1, "xxx")

Справедливости ради стоит сказать, что в Rust такое тоже есть.

Любой метод с &self можно вызывать напрямую как обычную функцию, принимающую первым параметром объект:

struct Foo(u32);

impl Foo {
    pub fn bar(&self, arg: u32) -> u32 { self.0 + arg }
}

fn main() {
    let foo = Foo(42);
    assert_eq!(Foo::bar(&foo, 1), 43);
}

То что упомянул Optik и про что написал я к ufcs особого отношения не имеет.


Разговор про синтаксический сахар, который позволяет делать read-/update-like методы. Это несколько похоже на operator() в плюсах (для read варианта). Т.е. self/this по-прежнему передается неявно (в отличии от того, что разрешает ufcs).

Т.е. a(x, y) как rvalue означает (эквивалентно) a.apply(x, y), а a(x, y) = z === a.update(x, y, z), IIRC

Это ближе, да. Только в случае скалы нет прибитого гвоздем количества аргументов, как если аналогичную вещь в расте реализовать через новый trait в std::ops.


Scala в смысле гибкости синтаксиса прекрасна. И, порой, ровно поэтому же ужасна: придумает кто-нибудь в очередной библиотеке оператор «гибкий кораблик» a <=≈=> b, и думай после этого где его искать и как по него гуглить (по мотивам реальной истории про аналогичный оператор).

Haskell тоже этим грешит и мне это откровенно не нравится. Мне кажется, что операторы таки должны быть стандартными, хотя и на стандартном наборе умудряются делать неочевидные или контринтуитивные вещи.
UFO just landed and posted this here
Да, безусловно, он есть. Другое дело, что сама ситуация, когда чтобы просто прочитать код приходится хуглить, уже вызывает недоумение.

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

Пока шел домой понял, что пример со звездочками не очень, ибо Rust-у, в общем-то, приходится делать то же самое. Гораздо показательнее в отношении C++ такой пример:

Где-то в тексте программы есть конструкция (a)(b) где a и b — некие идентификаторы. Надо выписать все возможные конструкции, которые могут за этим скрываться.

Вот тут уже без знания фактических типов a и b ничего распарсить не получится.
Где-то в тексте программы есть конструкция (a)(b) где a и b — некие идентификаторы. Надо выписать все возможные конструкции, которые могут за этим скрываться.
Расскажите тем, кто шапочно знаком с C++, что там может скрываться.

В C здесь может скрываться


  1. Приведение типа b к a. В C++ тоже есть.
  2. Вызов функции (я даже как‐то писал (cond ? func1 : func2)(args)) по указателю на функцию. В C++ вроде тоже должно быть.
  3. Только C++: вызов оператора вызова функции у b.
  4. Объявление функции. Компилятор по‐умолчанию подставляет int как возвращаемый тип, но если на контекст ограничений нет, то можно увидеть rettype (funcname)(argtype) и без нестандартного поведения компилятора.
  5. Вроде в C++ это может быть частью объявления с инициализацией.

В общем, три варианта только в C, и я ещё не уверен, что перечислил все. Кстати, если на контекст ограничений совсем нет, то ещё есть варианты «это часть строкового литерала», «это часть комментария» и «это то же, что и (b), потому что MACRO(a) раскрывается в пустоту».

Помимо этого (в зависимости от того, насколько вольно можно интерпретировать выражения a и b и что, что стоит с краев), в C++ это еще может быть:

  1. Часть объявления переменной (да, так можно и в C): (int)(x);
  2. Часть более длинной цепочки приведения (a)(b) …c
  3. Вызов оператора () у объекта a с параметром b уже сказали
  4. Цепочка вызовов оператора (): foo(a)(b)(c)
  5. Вызов функции-члена через указатель на нее, не то же самое, что просто через указатель на функцию
  6. Наверняка еще что-то забыл
Еще: левые скобки могут быть частью конструктора, а правые — применением оператора к полученному объекту: MyClass(a)(b).
Если же читерить с макросами, то можно вообще разгуляться:

  • Конструктор через вызов placement new, завернутый в макрос
  • Вызов любых операторов через синтаксис x.operator Y()
Про (int)(x) я похоже наврал, хотя вроде бы такое было возможно в старых компилтяорах. Сейчас проглатывает только вариант int (x); Зато таки можно написать int (x)(2);
UFO just landed and posted this here
А вы попробуйте добавить в синтаксис лиспа аннотации типов :)

У Racket есть типизированный диалект, выглядит вот так:


(define (foo [a : Number] [b : Number]) : Number
  (let ([c : Number (+ a b)])
    (* 2 c)))

(define (bar (a : Number) (b : Number)) : Number
  (let ((c : Number (+ a b)))
    (* 2 c)))

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


P.S. Вывод типов (для let, например) тоже есть.

UFO just landed and posted this here
Мне кажется или вы подтвердили мое предположение? :^)

Я с ним и не спорил. (:


Даже в не типизированном или, вернее, динамически типизированном варианте языка используются квадратные скобки. Правда сам на ракете не пишу, просто изучал из интереса, но кажется, что читаемость это заметно повышает. Есть ещё практика раскрашивать (в IDE) скобки в разные цвета, может тоже помогает, если приноровиться, не могу судить.


Насчёт префиксной формы не совсем понял, речь о следующем?


(: foo (Number Number -> Number))
(define (foo a b)
  (let ([c (+ a b)])
    (* 2 c)))

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

UFO just landed and posted this here
Возвращаясь к исходному замечанию про угловые скобки в шаблонах.

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

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

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


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

В современном Haskell принято писать прототипы функций.

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

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

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


Cистема сборки компилятора и стандартной библиотеки Rust была переписана на сам Rust с использованием Cargo

Ууу, rustbuild, сраное говно.
Вместо того, чтобы вести себя как приличные системы сборки и работать с графом зависимостей с таргетами в узлах и командами привязанными к таргетам, это наколеночное поделие херачит большую часть билда и тесты подряд в императивном стиле, режет параллелизм, добавляет зависимости. Когда совсем приспичивает приходится сравнивать timestamp'ы вручную (!) кодом на Rust.
Как же его хочется переписать, но нет возможности.
Хотя читается, конечно на порядок лучше старых мейкфайлов.

Спасибо за комментарий!
Поэтому пока сделали ровно необходимый минимум.
По поводу API я так и предполагал. Я так понимаю, в будущем будет возможность конвертации между `quote::Tokens` и `TokenStream`, или же оно вообще будет приведено к одному типу? Ну или через From/Into/AsRef.

Ууу, rustbuild, сраное говно.
А можно подробнее? Что куриличем руководствовались разработчики, почему было сделано именно так? Есть ли конкретные обрисованные планы развития? Вообще, я так понимаю, можно ответить вашими же словами, про то что сделали чтобы было, а потом это все будет допиливаться.
По поводу API я так и предполагал. Я так понимаю, в будущем будет возможность конвертации между quote::Tokens и TokenStream, или же оно вообще будет приведено к одному типу? Ну или через From/Into/AsRef.

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


Что куриличем руководствовались разработчики, почему было сделано именно так?


(a) leverage Cargo as much as possible and failing that (b) leverage Rust as much as possible!


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

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


Есть ли конкретные обрисованные планы развития?

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

UFO just landed and posted this here

В CONTRIBUTING.md достаточно подробно всё описано (в том числе как искать entry-level тикеты).
Для начала рекомендую найти какую-нибодь опечатку или функцию с плохой документацией, исправить и сдалать pull request на гитхабе просто чтобы опробовать процесс. Патчей такого рода много, ревьюеры доброжелательные и если что-то не так всегда подскажут, поправят + ревью и мердж достаточно оперативно происходят, не надо ждать месяц пока maintainer соизволит взглянуть на патч, как в некоторых проектах.

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

в некоторых проектах месяц — это очень повезло, а реальное время 3-9 месяцев, вот где затаилось зло

Меня все время мучали вопросы по поводу синтаксиса:

1. Почему у макросов нет пространства имен? Это же неудобно, начиная с того, что непонятно из какого пакета он тянется, и заканчивая конфликтами имен из разных библиотек. Почему не сделали так? (мне кажется это логичнее)
let v = std::vec![1, 2, 3];


2. Зачем нужно отдельно указывать extern_crate, почему просто не писать
use derive_new::new;

И если нет такого локального модуля, то воспринимать его как crate derive_new и качать зависимость.

Может есть этому разумные обьяснения, кто знает?
На второй вопрос могу предложить почитать https://withoutboats.github.io/blog/rust/2017/01/04/the-rust-module-system-is-too-confusing.html

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

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

План такой, что разрешение имен и разворачивание макросов будут работать вместе — "разрезолвили имена -> развенули макросы -> разрезолвили опять -> опять развернули" и так далее до точки равновесия.
Оно на самом деле уже так частично работает, просто работа не закончена еще.

Те макросы, которые есть сейчас (macro_rules!) это в некотором смысле временная мера, связанная с тем, что в 1.0 должны были быть макросы, но привести их в идеальное состояние не было времени.


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

Enterprise бывает разный.

Если просто как одно из средств разработки то да, можно. Базовые возможности языка стабилизированы начиная с версии 1.0. Этим гарантируется последующая обратная совместимость и что API внезапно не поменяется.

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

Для полноценного повседневного использования не хватает как минимум поддержки в IDE (которая вот-вот должна выйти из альфы) и развитой экосистемы библиотек.

Если говорить про веб, то лучше всего на этот вопрос ответит сайт arewewebyet.org.
Halt,

спасибо за информацию. А как насчёт дебагера в Rust?
gdb, lldb, все как обычно.
Eclipse + RustDT + GDB, VsCode + Rust + GDB. Насчет LLDB дебаггера пока информации не видел.
В GDB отображение содержимого тоже не ахти, особенно для хитро вложенных по ссылкам членов структур, или массивов, или некоторых объектов на куче. Но в большинстве случаев юзабельно.
Sign up to leave a comment.

Articles