Как стать автором
Обновить

Комментарии 399

Нормально так набросили.
собралось == работает
для написания абсолютно любых приложений
которые не задумываются о производительности
Признайтесь, вы специально? )

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


Я правда считаю, что раст продуктивный, и что его производительность — это хорошая, но далеко не единственная черта.И во многих случаях ей можно пожертвовать, получим очень простой и изящный код. В случае с тем же ботом у меня ~3-4 сетевых запроса на каждый пост пользователя: сначала обработка самого сообщения, затем запрос урлов файлов, затем запрос контента файлов, и потом еще и отправка сообщения в телеграм. Сколько относительно этого ест клонирование единственной строковой переменной?


С появлением async/await (уже есть в ночнике, я бота его недавно на него переписал) должна отойти самая насущная на сегодняшний день проблема, неудобный async IO на коллбеках. А других серьезных проблем, способных помешать продуктивно писать код, я не вижу. После некоторой практики на расте можно писать с той же скоростью, что и на C#, но получать намного более серьезные гарантии корректности. Да, эту практику нужно набить, но как я уже сказал в статье, "тяжело в учении — легко в бою", вы учите концепцию один раз, и дальше это как езда на велосипеде, всегда с вами до конца жизни. Затратили сколько-то времени, дальше этим пользуетесь, и чем раньше изучили, тем больше времени сэкономили.

А как у Раста с ORM-ами ну и вообще с поддержкой БД? Можете что-нибудь посоветовать?
Сам давно хотел попробовать что-нибудь кроме .NET на бекэнде. Но после EntityFramework как-то плеваться начинаешь на ORM-ы в других языках.
Есть Diesel, правда список поддерживаемых БД скромный — SQLite, PostgreSQL, и MySQL.
Дела с ORM: неплохо, но до EF конечно же пока не дотягивает. Кое-что есть примерно на уровне Linq2Sql. Классическая связка: r2d2 + diesel. Умеет в генерацию типов по схеме + LINQ-подобный dsl. Лично я проверял работу с постгресом, но есть разные провайдеры. Раньше не умел в миграции, сейчас в репе я что-то вижу на эту тему, но не пробовал.

Прям уровень EF это пока рановато, не зря он уже по сути восьмой версии (6 версий взрослого фреймворка, и две на Core). Но в целом, работать с БД можно.

Спасибо, почитал! Я так понял, что концепта UnitOfWork там нет?
Хотя вот это — огонь :)


// Using `include_str!` allows us to keep the SQL in a
// separate file, where our editor can give us SQL specific
// syntax highlighting.
sql_query(include_str!("complex_users_by_organization.sql"))

А аналог nameof() в Расте есть?

Есть макрос stringify!(), превращающий токены в строку. И есть крейт nameof на его основе, делающий примерно то же, что и nameof().

Спасибо, почитал! Я так понял, что концепта UnitOfWork там нет?

Ну вроде как let connection = pool.get(); это оно, нет?


А аналог nameof() в Расте есть?

нет, но мы же про раст говорим :) 2 секунды, и уже есть :)


macro_rules! name_of {
    ($x:ident) => {
        stringify!($x)
    }
}

fn main() {
    let my_variable = 10;
    let name = name_of!(my_variable);
    println!("{}", name);
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=2a2b3805c7d9b0cb291a0200f226f38c

let connection = pool.get(); это оно, нет?

Я имею в виду аналог DbContext:


post.published = true;
post.save_changes(&connection);

Если я правильно понимаю, save_changes(&connection) сохраняет прямо в базу. А группировка изменений по нескольким сущностям обеспечивается явной транзакцией. В противовес DbContext.saveChanges() из EF, который группирует изменения в один запрос.


name_of!(my_variable);

А name_of(Post::published) можно? В .NET я это использую для того, чтобы RAW SQL не требовал изменений после рефакторинга названий классов и полей.

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

А name_of(Post::published) можно? В .NET я это использую для того, чтобы RAW SQL не требовал изменений после рефакторинга названий классов и полей.

Ниже сказали, я немного неверно понимал как $ident работает. Нужно подумать еще.
Ниже отредактировал ответ.

> А name_of(Post::published) можно?

можно всё, что является идентификатором. Можно посмотреть на реальный макрос реального крейта, там из комментов понятно, какие случаи работают, и даже как именно их обрабатывать: docs.rs/nameof/1.0.1/src/nameof/lib.rs.html#72-93

Увы эта реализация не похожа на nameof из C#


macro_rules! name_of {
    ($x:ident) => {
        stringify!($x)
    }
}

fn main() {
    let my_variable = 10;
    let name = name_of!(my_variable_1);
    println!("{}", name);
}

выведет в консоль


my_variable_1

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=3ffad15450a43fabab337e37e22440aa
а C# код


using System;

public class Program
{
    public static void Main()
    {
        var myVariable = 10;
        Console.WriteLine(nameof(myVariable1));
    }
}

не скомпилируется с ошибкой:


Compilation error (line 8, col 28): The name 'myVariable1' does not exist in the current context
Compilation error (line 7, col 7): The variable 'myVariable' is assigned but its value is never used

https://dotnetfiddle.net/UHBtJh

Да, вы правы. Я забыл, что $ident может не только использовать существующий идентификатор, но и новый объявлять. Беру паузу на размышление :)




Подглядел в крейт nameof, чтобы посмотреть, как они это сделали. Собственно, так же, как и я, только с гвардом, который как-то использует переменную: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=40fdb6ff604b9f094faaaaf40ee826cb

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

можно всё, что является идентификатором. Можно посмотреть на реальный макрос реального крейта, там из комментов понятно, какие случаи работают, и даже как именно их обрабатывать: docs.rs/nameof/1.0.1/src/nameof/lib.rs.html#72-93
Код великолепен — как древние С- макросы, только с привкусом брейнфака.
От сишных «макросов» тут одно название. Это ближе к лиспу и работе с AST, а не тупая автозамена. Если с лиспом работали, то понимаете, насколько это мощная концепция. Мы буквально за пару минут реализовали фичу языка, которую в шарпах делали несколько месяцев, и которую еще несколько лет ждали в релизе. Как по мне, это успех.
Ошибки позволяет делать абсолютно любой язык. Просто некоторые в ответ отказываются компилировать код (или, напротив, компилируют то, что компилироваться, по задумке, не должно), а некоторые — доводят ошибку до рантайма и выдают какую-то ересь. Думаю, Вы не станете спорить, что Rust здесь себя проявил как язык из первой категории.
Ну называйте их не макросами, а плагинами компилятора, если вам так проще. Вы вот LLVM пользуетесь? Там есть трансформаторы кода, оптимизаторы всякие. По сути достаточно близко. Это тоже зло?
Текстовая подстановка — макросы, доступ к информации компилятора — трейты.

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

И нет, писать на LLVM коде я не планирую — как и ассемблер — он небезопасен в использовании. Хороший язык должен максимально обходиться своими средствами, без всяких там asm/llvm вставок и трейтов тоже. А макросы просто устарели, типобезопасные шаблоны(генерики) — на две головы впереди.
Ну «дыра» — это громко сказано. Из-за того, что может быть кто-то недопонял или забыл, что макросы работают на синтаксическом, а не семантическом уровне, сразу злорадствовать о дырах… такое себе. Естественно, от подобных логических ошибок никакой компилятор не защитит, и тесты писать нужно, хотя бы простейшие.
Это не логическая ошибка. Это именно пц

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

Кому надо? Зачем надо?

НЛО прилетело и опубликовало эту надпись здесь
По сути так и сделано, но это решено отдельными крейтами, в частности syn и quote. Из преимуществ, можно сделать более удобный крейт для парсинга, не трогая компилятор. В общем, проблем с этим нет, с сырым потоком токенов никто, конечно же, не работает.
Всем надо. Граблеопасная техника.

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

Так «в чем опасность-то»? Или это «очевидно» и в обосновании не нуждается?
Т.е сообразить самостоятельно после сделанной ошибки не получилось?

Вообще то после некоторого опыта конечно очевидно. Ослабляет проверку типов.

Но еще есть и гугл и учебники. В частности, в Misra C 2004 правило 93 как рекомендация не использовать, и в Misra C++ 2008 правило 6-2-2 вообще запрещающее функциональные макросы.
А при чем тут правила С? Я вам уже говорил, что «Макрос» в С и расте это две совершенно разные штуковины. Так же, как слово Лист в русском и List в английском означают немного разные вещи.
А сосед-растишка — говорил обратное. Поскольку я не знаю, кому верить, закругляюсь.

Но поскольку ошибка в наличии, она таки требует объяснений.
А сосед-растишка — говорил обратное. Поскольку я не знаю, кому верить, закругляюсь.

Там ничего не написано про "макросы в расте такие же как в С". И я предложил бы вам вежливее отзываться о ваших коллегах-программистах.


Но поскольку ошибка в наличии, она таки требует объяснений.

Ошибка в том, что я забыл, что $ident должен являться валидным идентификатором. Но в момент вызова макроса мы этот идентификатор и создаем. Этим можно воспользоваться, например, для генерации бойлерплейт-кода:


macro_rules! construct_uint {
    ($name:ident, $n_words:tt) => (
        #[repr(C)]
        #[derive(Copy, Clone, Eq, PartialEq, Hash)]
        pub struct $name(pub [u64; $n_words]);

        impl $name {
            pub const MAX: $name = $name([u64::max_value(); $n_words]);
        }
    )
}

construct_uint!(U128, 2);
construct_uint!(U256, 4);
construct_uint!(U512, 8);

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


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


Насчет дыры, это не большая дыра, чем такой код


fn add(a: i32, b: i32) -> i32 { a - b }

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

А чем не нравится «растишка» (без негатива)?
Я честно перебрал термины — пишет на С — сишник, на паскале — паскалист, на расте = ??? растер, растишник. Готов к предложениям.

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

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

Потом еще глаз зацепился за unsafe в Особо Опасной функции вывода в строку.

На этом мои изыскания по расту окончены. Спс за общение.

P.S Код по ссылке выше, — можно вполне брать за образец и переписывать под свой язык — написан симпатично — это библиотечка работы с целыми числами любой разрядности
А чем не нравится «растишка» (без негатива)?
Я честно перебрал термины — пишет на С — сишник, на паскале — паскалист, на расте = ??? растер, растишник. Готов к предложениям.

Английский вариант — rustacean, русский, видимо, растовчанин.


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

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


Потом еще глаз зацепился за unsafe в Особо Опасной функции вывода в строку.

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


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

-ишк
Словообразовательная единица (суффикс)
1. под ударением при добавлении к основе существительного образует существительное со значением пренебрежительности
https://ru.wiktionary.org/wiki/-ишк
Английский вариант — rustacean, русский, видимо, растовчанин.

Растовщик, растаман.
Текстовая подстановка — макросы

Макросы не занимаются текстовой подстановкой, если только это не С.

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

Если бы я писал реальный код, я бы написал два теста: на успешное и неуспешное прохождение, и отловил бы ошибку. Да, от вообще всех ошибок раст не спасает.

И нет, писать на LLVM коде я не планирую — как и ассемблер — он небезопасен в использовании. Хороший язык должен максимально обходиться своими средствами, без всяких там asm/llvm вставок и трейтов тоже.

То есть все языки, собирающиеся в LLVM стали дырявыми?

А макросы просто устарели, типобезопасные шаблоны(генерики) — на две головы впереди.

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

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

А можно увидеть это заявление? А то пока походит больше на strawmen аргументацию.


Остальные пункты вы, видимо, решили проигнорировать.

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

про остальное я не вижу конкретных вопросов. про макросы — ваш сочувствующий съехал на «синтаксическую составляющую», например, и что это нормально :fail:

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

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

Как я и говорил — Растом не понимаю.

Но тебе большой вопрос — понимаешь ли ты разницу и можешь ли сказать однозначно — макросы в расте это просто синтаксическая подстановка (как заявлялось ранее) или компилируемый шаблон (который с проверкой типов)?

Потому что своим недопониманием (или недостаточным скиллом объяснений) ты подставляешь всё раст-сообщество как класс.

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


macro_rules! foo {
    ($($t:tt)*) => {};
}

fn main() {
    foo!(Fuck your semantic!);
}

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

ИМХО, как раз таки макросы это крутая фича как в том же Clojure
И совершенно игнорируют тот факт, что это хороший язык общего назначения, на котором и микросервисы серверлесные можно делать, и другие интересные штуки

Все упирается в то что крейтов или нет вообще, или заброшенные, или несовместимые.


Я вот недавно писал мелкий сервис который болтает по GRPC, конвертит картинки в jpg/webp, кропает, и заливает в google cloud storage. Три дня на го.


И я хотел бы написать то же на расте, но с картинками как-то все сложно, с API к GCS тоже, ну вот и все — интерес кончился, надо чтоб работало.

С этим трудно поспорить. Я полгода потратил на написание cv-rs (биниднги к opencv), чтобы мой бот мог картинки анализировать. Было бы намного проще, если бы это кто-то сделал до меня.


Но для обычного приложения в стиле MVC с DAL слоем уже всё есть. Для специфики могут быть свои нюансы.

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

grpc, webp, google cloud
Ну было бы странно, если бы на go не было всё готовое для использования этих технологий.

GRPC официально работает на C++, Java, Python, Go, Ruby, C#, Node.js, Objective-C, PHP, Dart и в броузере.
GCS API биндинги есть для C++, C#, Go, Java, Node.js, PHP, Python, Ruby.


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


В rust есть набор костылей (объективно — тот же порт grpc не умеет несколько вещей которые мне нужны) и конструктор "сделай лего сам". Там где в PHP вы компонуете компоненты, хук, хук и впродакшен, на расте надо все еще писать обертки к API.


Я не говорю что раст плохой, я говорю что у них (в отличие от того же Go/Python) "batteries are not included", а в cargo разброд и шатание.

Простите, а в каком месте actix «точно такой же фреймворк» как Akka?
Там только акторы и всё. В акка есть кластер, стримы, http наконец и многое другое.
Этот фреймворк далеко не просто акторы, в нем много библиотек выстраивающих экосистему.
Все равно что сказать, Spring в Java это DI ))
Http в актикс есть, причем http2 автор добавил за пару недель, что ли, причем самостоятельно без помощи. Refactoring is a pleasure — действительно так.

Кластеризация это да, мощная штука, но как уже говорил парень с дотнекста «Я вот щас вам рассказал про кластеры, но если есть возможность, НЕ ДЕЛАЙТЕ их» :)

В данном случае, возможно, вы получите такой буст по производительности. что и кластер уже не нужен будет. И это снимет целую кучу потенциальных проблем.
С душой написано, спасибо. Пожалуй, на праздниках попробую-таки раст.
Да, я как С++-разработчик, получил удовольствие) Именно так и должны выглядеть пропагандистские статьи ;)
Прямо озвучили мои мысли. Собрался на праздники в места далекие от интернетов и решил, раз уж такое дело, затариться литературой по расту.
А автору — благодарность за труды.
Rust страшен первые недели две. Потом только в удовольствие.

Хотел этот вопрос задать в интернетах, но раз уж такая пляска, спрошу тут:


Вчера писал калькулятор на расте. Суть в том, что можно написать "2 + 2", он это отпарсит и вернет ответ. Я сделал это по привычке на ООП: для выбора действия, я сделал трейт Operation с единственным методом run(i32, i32) -> i32. Сделал структуры с этим трейтом (самы структуры получились пустыми, в них нет состояния) и положил их в словарь в виде трейт объектов, которые дергаются по требованию.


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

Ну, полагаю, rust-way более функциональный в данном случае. Там, где в ООП вы делаете абстрактный класс и кучу наследников, в ФП вы делаете один энум и матчите его в тех местах, где вам нужно. Где-то это дает выигрыш, где-то нет, это известная проблема выражения.. И для раста это выходит более естественно, чем прямой перенос ООП опыта. Я в серьезных крейтах трейт-объектов вообще не встречал, динамическая диспетчеризация используется очень редко.


Вот пример крейта, построенного достаточно идеоматично: https://github.com/z2oh/sexe/blob/master/sexe-expression/src/lib.rs

НЛО прилетело и опубликовало эту надпись здесь
Там, где в ООП вы делаете абстрактный класс и кучу наследников, в ФП вы делаете один энум и матчите его в тех местах, где вам нужно

так это же по сути «смешать код в кучу» вместо инкапсуляции?

Не совсем, просто подход иной.


К слову, в ООП этот паттерн называется "Visitor", и используется в случаях известного множества классов чуть чаще, чем всегда. Поэтому если вы работали с AST, например, и писали VisitConstant/VisitBlock/VisitCondition/..., то это оно и есть.

а если множество классов заранее неизвестно?
тогда tagless final. Точно так же, как в ООП есть паттерн Visitor для статических иерархий, в ФП есть паттерн для динамических.

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


К слову сказать, в Расте есть замыкания (которые "под сахаром" на самом деле тоже структуры).

У вашего варианта есть фатальный недостаток. Что если вам понадобятся унарные или тринарные операции?


В функциональных языках есть один очень популярный паттерн — интерпретатор. Реализуется он обычно либо при помощи tagless final кодирования выражений, либо при помощи GADT. GADT в Rust нету, а вот простенький tagless final мы можем сделать используя трейты.


Можно объявить трейт Expression


trait Expression {
  fn add(&self, right: &Self) -> Self;
  fn sub(&self, right: &Self) -> Self;
  fn negate(&self) -> Self;
  fn eq(&self, right: &Self) -> Bool;
}

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


Для расширения Expression можно использовать "наследование" трейтов (хотя в большинстве случаев будет проще и лучше запихать операцию в изначальный трейт):


trait ExpressionMul : Expression {
  fn mul(&self, right: &Self) -> Self;
}

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


fn eval(i: i64) -> i64 { i }
fn stringify(s: String) -> String { s }
fn double<E1: ExpressionMul, E2: ExpressionMul>(pair: (E1, E2)) -> (E1, E2) { pair } 
Дополню про tagless final хорошей статьей, которая объясняет, как именно этот подход решает упомянутую проблему выражения, примеры на хаскелле и джаве.
Использовать трейты и структуры — вполне себе идиоматично. А вот трейт-объектов старайтесь избегать: применяйте их только там, где действительно другие способы не работают.
Прошу прощения за занудство, но вы решаете задачу неправильно и на ООП языке. Вычисление выражения с учётом приоритета операций и скобок не требует постройки деревьев, один довольно простой стековый автомат нужен чтоб преобразовать это выражение в бесскобочную обратную польскую запись например «a + b*c» -> «a b c * +», а "(a + b)*c" -> «a b + c*». Вычисление выражения, записанного в обратной польской записи реализуется на стековом автомате ещё проще. Можно объединить и получить автомат с 2мя стеками, который считает прямо. Стеки в расте есть.

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

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


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

Это очень ведь дорого...

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


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

Согласен. Мне лично видятся очень хорошими варианты erlang+rust и golang+rust, и если выйдет история и webassembly, то js+rust.
я научился писать простые сниппеты без ошибок с первого раза.
Это очень ведь дорого...

Зависит от того, с чем сравнивать:


  • Можно взять php, начать писать на нём прямо здесь и сейчас, очень дешево вначале, дорого, когда в файле больше 200 строк и очень дорого в поддержке
  • Можно взять js, на котором тоже можно быстро начать писать, но неявные приведения типов могут склонить вас сменить профессию с программирования на проституцию
  • Можно взять C++, относительно дешево выучить его, писать проектики, а потом три месяца дебажить неопределенное поведение силами двух программистов с суммарным опытом в 20 лет.

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


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


  • отсутствие большого количества нормальных production-ready пакетов, на данный момент на вкус и цвет 10000+ пакетов, годных и вылизанных от и до всего чуть больше 100-200 (а у С++ и того нет). Поправьте, если ошибаюсь.
  • неоправданно большой граф зависимостей для некоторых пакетов (как если бы 90% npm зависили от left-pad).
  • 200 пакетов компилируются за минуту, хотелось бы быстрее
  • На данном этапе не понимаю, как интегрировать Rust futures в Js Promise для асинхронного WebSocket, чтобы перенести свой проект из нативного приложения в WASM. Решения на колбеках есть, но хотелось бы чего-то готового и поддержки в tokio/romio. Программистам на Go в этом плане сильно повезло, у них таких проблем нет. Они находятся на этапе: "почему мой hello world на WASM весит несколько мегабайт. Какой рантайм, какой ГЦ? Что это такое? Почему плохой транслятор GO-WASM тащит весь язык в файлик .wasm?"
  • устал от HR, приглашают на работу и собеседования каждую неделю. Приходится вслушиваться в произношение людей из Шотландии и Новой Зеландии
  • приходится скрывать доходы от друзей, потому что зарплату в $$$ реально девать некуда.
А можно молотком забивать гвозди, а шилом делать дырки.

Я лишь про то, что rust прекрасный инструмент, когда мне нужен максимум скорости. Условно php7 может дать 30-40% от максимума; java — 60%; golang — 80%; и rust, c — 100%. Но в то же время чем ближе к C, тем каждую фичу ждать дольше и дольше, это с одной стороны. С другой, мы можем оценивать задачу по тому, сколько ей надо производительности и соответственно выбирать инструмент.

Про $$$ и golang вы тоже будете удивлены. Скажем так, 150-170k usd год на удаленной работе — не есть большая сложность.
Я лишь про то, что rust прекрасный инструмент, когда мне нужен максимум скорости.

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


Когда мне надо наговнякать хоум страничку для друга, на которую никто не будет заходить — я выбираю PHP.
Когда мне надо в браузере работать с UI — я выбираю JS.
Когда мне надо набросать функциональщину для проверки гипотезы — я выбираю Haskell.
Когда мне нужен самый простой язык в мире, у которого 3 разных типа обработки исключений — нет, не выбираю :D


И когда мне нужен безопасный язык с предсказуемым поведением, который может поддерживать разработчик-июнь за 400USD, я выбираю Пикачу Rust, а не тот язык, поддержка которого стоит 150-170k USD в месяц (и то не предел).

Вопрос денег подняли вы.


Как план с junior должен сработать? Через какое время на rust он сможет полноценно закрывать задачи?


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

Как план с junior должен сработать? Через какое время на rust он сможет полноценно закрывать задачи?

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


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

Ага. Чтобы корова меньше ела и давала больше молока ее нужно чаще доить и реже кормить.


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

А в статье у автора ушел год и только простые сниппеты с первого раза выходят… И это опытный разработчик.
Что-то не так в свитках...

А вы часто пишете на JS код (без IDE), который 100% работает во всех граничных случаях, не складывает случайно строки с числами и т.п. с первого раза? Или С++ код, на который ни один анализатор не ругнется ни одним правилом?
Я лишь про то, что rust прекрасный инструмент, когда мне нужен максимум скорости.

Такое ощущение, что статью вы вообще не читали.

Читал. А вы мой изначальный комментарий?
"Хорошая статья.
Правда я придерживаюсь мнения, что язык, скорее системный, и мне проще и в пяток раз быстрее делать сервисы на го, а в раст выносить криптографию, системные вещи. Иначе цена решения становится несоразмерной."

Читал. И, как можно догадаться, не согласен. От того, что вы решение некоторых проблем вынесете «на потом», в рантайм, лучше не становится. Вместо того, чтобы сделать фичу за 3 дня, делаем за день, а потом еще 2 дня дебажимся. Зато количество закрытых тасок удвоили. Вот замечательно-то.

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

Как же все только живут с этими проблемами на рантайме… Тесты пишут, как и разработчики rust.


Если говорить не голословно, то пока я видел одну неудачную миграцию на rust, которая сильно увеличила сроки проекта и поставила его под вопрос. Сейчас очень хочу узнать не об опыте крутых, без всякого сомнения, одиночек, а о больших и долгих проектах.
FF с его 6% кода на rust не впечатлил пока, динамика там есть, но пока не видно, чтоб он занял существенную долю.


И, повторюсь, вопрос долгой поддержки. Мне лично неясна ниша языка, пока мы не увидели проекты в долгой перспективе. У нас уже есть клевые scala, haskell, но которые слишком дороги в поддержке. Мне хотелось бы, чтобы rust не повторил эту судьбу.

Как же все только живут с этими проблемами на рантайме…

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

Тесты пишут, как и разработчики rust.

В шарпе вы не пишете тесты на то, что вместо числа придет «ff», «qwerty» или ящереца в стакане, в JS пишете.
В расте вы не пишете тест на то, что в многопоточном окружении ваш код не сломается, в тех же шарпах — пишите.

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

В статье про PVS studio было прилично ссылок. Из того, что на слуху, можно вспомнить Parity/Exonum/Redox, например.

FF с его 6% кода на rust не впечатлил пока, динамика там есть, но пока не видно, чтоб он занял существенную долю.

Когда я последний раз смотрел статистику по репозиторию, в FF было 1млн строк кода на С, 1.5 миллиона на расте, 3.5 (или 7, не помню точно) миллиона на С++, и около 5 миллионов всякой шелухи вроде html.

И, повторюсь, вопрос долгой поддержки. Мне лично неясна ниша языка, пока мы не увидели проекты в долгой перспективе. У нас уже есть клевые scala, haskell, но которые слишком дороги в поддержке. Мне хотелось бы, чтобы rust не повторил эту судьбу.

Буквально в прошлом месяце у меня знакомый сменил синиор шарпа позицию на синиор скалиста. Не так уж у неё все плохо.

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

Статистику тут обычно смотрю https://4e6.github.io/firefox-lang-stats/


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

Огромное спасибо, давно искал эту ссылку! Сильно наврал с цифрами, прошу прощения. Помню примерно «1.7 раста на 7 С++», остальное хуже.

Да, то верная информация.

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

Откуда взятся проектам сильно за год, если язык только 3 года назад в 1.0 вышел? Ведь там менеджеры тоже по той же логие смотрят «пока только появилось, надо обождать, присмотреться, и только потом осваивать».

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

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

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


Мне лично пока видится, что врядли и нет. Посмотрим.

Я извиняюсь если вопрос покажется некорректным. Но мне правда интересно. Вы бы стали писать на Golang если бы за ним не стоял Гугл? И что произойдёт если гипотетически Гугл скажет "голанг неудачен, пилим всё на тайпскрипт"?
Озвученная вами проблема — извечная проблема курицы и яйца. Никто не хочет писать на новом языке т.к. на нём не пишут толстые корпорации — которые на нём не пишут т.к. пишет мало кто, goto 1. Почти гарантирую, что если бы С++ был создан сейчас в текущем виде, он бы помер не родившись — но его держат мегатонны легаси.

Хороший вопрос. У меня в активе языков есть такие вещи как R, closureScript, так что вряд ли я гонюсь за популярными вещами и большими компаниями.

Да, я выбирал язык не из-за Гугла. У меня был PHP, Python, плюс всякое редкое (по месяцу пробовал Nim, Crystal), но не было чего-то достаточно быстрого, клево себя в concurrency и строго типизированного. Рассматривал варианты C#, с которым был год опыта, когда он еще был версий 1.1-1.3, Java, С++, golang.
C# отмелся поскольку совсем другой стек все же. Хотя как язык он мне очень нравился.
Java — слишком большая штука. Ее надо брать не дополнительным инструментом, а единственным и для всего. Но окончательно я ее не отметал.
С++ — я еще помню долгие споры об Oberon/modula/Pascal vs C/C++ и брать язык с всевозрастающей собственной сложностью — это точно нет.
Golang обещал полную обратную совместимость (слово сдержано и с 1.0 по 1.13 ломающих изменений было ровно 2, которые фиксились автоматически гошной же тулзой), приятное мне смещение внимания с языка на продукт (когда можно не изучать и изучать язык каждый год, а заниматься развитием продуктов), почти полная имплементация CSP (за исключением операции удаления потока), очень (очень-очень) быстрая компиляция, что делала работу в TDD удобной и комфортной. Это перевесило, я начал его учить. Скоро и вакансия нашлась.

Нет, про гугл я тогда не думал. В основном думал про те задачи, которые хотелось решать. А это был e-commerse и highload. Там golang себя хорошо нашел. Сейчас занимаюсь распределенными системами и golang себя все еще хорошо чувствует. Как бонус получил golang на мобильных устройствах и есть опыт уже разработки гошных либ для мобильных приложений.

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

Как-то так. Не думаю, что ваше дальнейшее рассуждение о курице и яйце ко мне применимо. У меня другие критерии.
Проще для кого? Для человека с 2 годрами опыта на Rust и без опыт Go Проще в пяток раз будет на Rust сделать весь миросервис и работать он будет быстрее и надежнее. Да в целом код на Rust писать мне приятнее и проще чем на Go хотя и там и там у меня опыт одинаковой величины. Просто потому что в Rust есть шикарные enum и генерики поэтому не приходиться костыли делать как в Go с interface{} или вообще, прости господи, кодогенерацией.

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

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

Это вы начали давать субъективные оценки. Я вам лишь ответил на вашем же языке.

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

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

У меня есть в команде разработчик: 2 года go, затем год rust, и вот снова go.
На нем удобно сравнивать. На нем и сравниваем.
Задачи на го идут шустрее в разы.


Ну и я тоже изучаю раст сейчас, пока то, что я вижу точно говорит:


  1. Код ревью будут сложными и могут быть долгими
  2. Вход разработчика в проект тоже долгий
  3. Начала работы новичка в языке — несколько месяцев и точно нужно приставлять наставника.

Изучаю дальше.

Вам половину ревью сделают компилятор, rustfmt и clippy. Писать на Rust новый код чуть долше, но вот рефакторить ранее написанный — довольно быстро и приятно. А главное — появляется уверенность в его надежной работе с технической стороны, можно больше внимания уделять проверке логики (даже по сравнению с Java, где у меня на большом проекте примерно 80% времни уходило на исправления NPE-багов).
Все равно надо код ревьюить и пока мне видится, что при большом объеме фич в языке и довольно «особенном» синтаксисе, подчас, приведет к трудным ревью. Это вопрос, с какой скоростью можно понимать чужой код на rust.
Для меня вопрос открытый, хоть и есть свое мнение.
Фич не так уж много, как может показаться. Растбук сравнительно небольшой (реально прочитать за день, если с растом хоть немного знаком), и покрывает всё, что есть в языке.

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

Это вопрос, с какой скоростью можно понимать чужой код на rust.

Раст это не «новый С++», там нет непересекающихся подмножеств языка, где каждый разработчик пишет на «своём» диалекте и не понимает соседей.

Новая версия раста выходир за в 6 недель, и как правило выходит какая-нибудь мелкая фича, как правило, снятие существовавшего раннее ограничения, стабилизация пары полезностей в стандартой библиотеке и прочая мелочь. Раз в год примерно выходят крупные фичи, вроде того же NLL, или готовящегося async/await и генераторов. Не сказал бы, что это прям очень часто, какой-нибудь C# обновляется примерно так же раз в год.
Раст это не «новый С++», там нет непересекающихся подмножеств языка, где каждый разработчик пишет на «своём» диалекте и не понимает соседей.

Мне это пока сомнительно. Пока выдится именно так. И повторюсь, в сравнении с golang, который поддерживает полную обратную совместимость и даже golang 2.0 взял на себя гарантию, что код любой версии 1.0 будет компилироваться без изменений, изменения в rust смотрятся дополнительной ценой, которую надо платить.

Возможно через полгодика изучения языка количество фич и особенностей не будет казаться таким большим, но пока оно именно такое и пока думается, что это вряд ли изменится, все же язык явно наследник именно c++.
Мне это пока сомнительно. Пока выдится именно так. И повторюсь, в сравнении с golang, который поддерживает полную обратную совместимость и даже golang 2.0 взял на себя гарантию, что код любой версии 1.0 будет компилироваться без изменений, изменения в rust смотрятся дополнительной ценой, которую надо платить.

В расте тоже полная обратная совместимость. Более того, Rust 2018 остается совместимым настолько, что вы можете иметь крейт 2018, который ссылается на 2015, который тоже ссылается на 2018, и всё это будет работать.

А вот будет ли совместимым Go 2.0 — не уверен. Судя по генерикам — вряд ли.

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

Мое мнение, что аналогия ложная. Впрочем, вам решать.
Поясните про генерики и 2.0. Старый гошный код не должен от них ломаться, судя по драфту.

Ну хорошо, если так.


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


Если команда го всё это смогла учесть, то могу их только поздравить.

Бинго! Нет перегрузки.


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

А я знаю пример, когда разработчик "въехал" и начал нормально писать на расте за пару недель. До этого он писал на го и скале.

И свободно говорит на 15-языках?

Речь про начинающих.
Я бы рекомендовал посмотреть VanquisherWinbringer публикации и код из них, прежде чем обсуждать с ним и делать далеко идущие выводы =)

На мой взгляд, код Раста трудночитаем -> мало кто будет на нем писать со всеми вытекающими. Так что согласен.
Я б рад, но времени не так много, чтобы дополнительные статьи читать.
Тут, скорее, было интересно понять, как складывается отечественное сообщество вокруг языка. Оно определенно складывается. И, к сожалению, должен признать, что оно пока более дружелюбно, чем в свое время гошное. Хотя и тут не без криков о вкусах и понятий вроде «ненавижу язык Х»!

А вы, товарищ, не пробовали rust? Было бы интересно обсудить.
Думаю, не буду участвовать в истерии. Кроме синтаксиса и практического неудобства, раст еще и сырой.

Захочется приключений — вернусь в dlang — он на 5 лет старше и хотя бы прошел детские болезни и оброс фремворками. Ну или уж посмотрю на golang — он практически стабилизировался (еще бы ввели ожидаемую обработку ошибок).

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

Захочется приключений — вернусь в dlang — он на 5 лет старше и хотя бы прошел детские болезни и оброс фремворками.
Где-то можно посмотреть список живых фреймворков для D? На слуху как-то кроме Vibe.d ничего и нет.
code.dlang.org причем с версионированием пакетов и родной системой сборки.

Но D я тоже хвалить не буду — у него тоже есть свои критические (на мой взгляд) недостатки.
Жаль, ответа по сути не будет. Куча пакетов с номерами версий вроде 0.0.6 или 0.2.0 — на «оброс фремворками» не тянет. Даже если вести отсчет от появления D2.
Ну «маемо, шо маемо». Это ж, опенсорс =)

Vibed тоже имеет версию 0.8, но проходит все тесты…
Ну просто если сравнить с тем же crates.io, то можно сказать, что в D ничего-то и нет. 1.5K пакетов для D против 21K оных для Rust-а.
На мой взгляд, код Раста трудночитаем -> мало кто будет на нем писать со всеми вытекающими. Так что согласен.

Посмотрим. Мне изначально код раста казался очень даже читаемым, и постепенно это чувство только крепнет. Ну вот возьмем например мою версию телеграм-клиента. Он очень простой, умеет вызывать несколько методов удаленного сервера, и держать соединения. Какие конкретно места по-вашему тут трудночитаемые?

Фу таким быть — обычно к подходу, у меня больше опыта поэтому я знаю как лучше прибегают глуповатые люди не способные аргументировать свое мнение. Поэтому я бы к вашим комментариям тоже отнесся скептически. Ну раз уж на то пошло то я лично участвовал в разработке информационной системы международного уровня, системы федерального значения и одной системы регионального уровня. Да это все было на C#. + Разрабатывал и проектировал с нуля клиент — серверное приложение для одной Туристической компании которым они года два пользовались. Сейчас как там дела уже не знаю. Я же не буду сюда выкладывать код с тех проектов. Да и на гитхабе всякую фигню делаю как попало для души. И да, я больше десятка разных языков из любопытства пробовал и пока что мне из них Rust нравиться.
Вообще то в моем комментарии нет абсолютно никакого негатива. Каждый пусть делает выводы сам.

Только легкое подкалывание Эксперта по языку со статьей от 5 ноября 18г, «Изучаю Rust....» =)
Rust трудночитаем только для тех, кто на нем не программирует. Потому что синтаксис непривычный. Но он довольно простой и проблем с восприятием чужого кода, если он не перегружен лайфтаймами (что бывает редко), обычно не возникает. Читать исходники зависимостей проекта — обычная практика в Rust. Часто это быстрее и проще, чем смотреть документацию.
Сейчас отсюда последует leap of logic, что с документаций в расте все плохо :)

И ведь подумал, что надо дописать: "хотя с документацией в Rust все в порядке", но не стал. А зря :)

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

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

Rust source — WTF ???
Языки семейства ML и Haskell, например, входят в ваш десяток? Может быть те, которые вы можете читать бегло — это всё вариации примерно одного и того же языка (или двух)?

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

Можно взять C++, относительно дешево выучить его, писать проектики, а потом три месяца дебажить неопределенное поведение силами двух программистов с суммарным опытом в 20 лет.
А как именно Rust защищает от ошибок с низкоуровневой работой с неправильно выровненными данными (ведь ваша ссылку ведет на исправление именно такой проблемы)?

О, старый друг по комментариям!
Это действительно меткий вопрос, я очень рад, что вы прошли по ссылкам и осознали мою боль. Я хорошо подумаю над ответом и дам его в развёрнутом виде с пруфлинками и всем таким, когда приеду домой.

Rust не защищает тебя на 100% одним своим присутствием в проекте, но позволяет вывести строгий аргумент безопасности, которые сведут количество подобных ошибок к минимуму, если не к нулю: не пиши unsafe. Если они и возникают, то ты всегда ищешь там «где светло», а светло там, где есть unsafe.


А как именно Rust защищает

У меня вчера был сложный вечер. Меня порывало сказать: "Да никак! Это настолько сложная ошибка, что даже Rust пасует перед ней!", потому что я был под влиянием неприятных воспоминаний, когда не было никакой возможности найти зацепку в коде C++, а только лишь дебажить и дебажить. Как получить зацепку для дебага кода C++, если он весь unsafe со списком в ~200 неопределенных поведений? Ну… делать вот так o_O и искать, искать, искать. Ошибки подстерегали нас на каждом углу, даже сложению знаковых чисел нельзя было доверять. Хотя казалось бы, самая примитивная операция.


На этом можно было бы и закончить комментарий, мол, Rust не защищает, но тут в дело вступает маленький нюанс: а где бы я мог выстрелить в ногу, если бы я писал виртуальную машину на Rust? Только в unsafe. И путем нехитрых изысканий приходим к тому, что unsafe мне бы нужен был только для низкоуровневой работы с памятью. Всё. Делаем o_O на 500 строчках кода, обмазываемся тестами, перепроверяем только код сборщика мусора. Rust выигрывает не в своем фанатичном "безопасно", а в предоставлении системного подхода к поиску низкоуровневых проблем. Ищи там, «где светло».


Что делать, когда этого «светло» нет? Казалось бы, есть ноды https://nodes.tox.chat/, написанные на C, которые уже 5 лет в проде, оттебажены, в которых все хорошо, которые не падают… Ведь так?


The Red Downtime


Вот ты разработчик, у тебя падает код в 60KLOC, который 5 лет "правильно работал". Что ты будешь делать? Я бы заплакал.


Ну и в противовес: tox-rs (30KLOC) без единого unsafe. И наш сервер не падает в рандомных местах. Не утекает, не ломается. И не утечет, не сломается.


Тут стоит напомнить о свежей баге, найденной в tar, которому 40 лет:


https://utcc.utoronto.ca/~cks/space/blog/sysadmin/TarFindingTruncateBug


if you run GNU Tar with --sparse and a file shrinks while tar is reading it, tar fails to properly handle the resulting earlier than expected end of file. If the file grows again, tar recovers.

Зато на C. И очень быстра.

Грубо говоря, вы слились.

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

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

Действительно грубо.

А можете привести пример проекта вот с этими свойствами (желательно со ссылками, подтверждающими эти свойства):
на плюсах совсем не сложно сделать

сервер, который не падает, не утекает, не ломается

Мы делали это


Интересно узнать о проектах какой сложности идет речь.
Действительно грубо.
Как народец-то обмельчал. Слово «слились» — это уже грубо. Суппорт старого кода всего в 60KLOC — «я б заплакал».
А можете привести
Из публично доступного у нас есть вот это. Игрушка, с собственной реализацией HTTP-сервера.
Интересно
Мне вот было интересно узнать, как Rust защищает от ошибок выравнивания данных в низкоуровневом коде. Но что-то не вышло.

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

Если только синтаксически значительно сужает зону, где они могут появиться, чем облегчает и кодирование, и поиск ошибки. Но не радикально.
В C/C++ то же самое достигается с помощью правильной архитектуры, строгого следования code guidlines и статического анализа. Т.е. более трудоемко. Это цена большей выразительности (на низком уровне — точно) и излишней мягкости Б. Страуструпа.
О том и речь.

Причем я бы не стал смешивать C и С++ здесь. Т.к. в С++ столкнувшись с такой ошибкой можно было бы сделать шаблонный тип вроде properly_aligned_ptr<T> и изменить API так, чтобы интерфейс оперировал такими типами, а не голыми указателями. Тогда как в C пришлось бы следовать устным договоренностям.
Тогда как в C пришлось бы следовать устным договоренностям.

То есть когда у C++ может быть защита на уровне интерфейсов — это хорошо, а когда у Rust есть защита на уровне интерфейсов по дефолту плюс усиленная защиту на уровне типов — это хипстота, смузи и закопайте?


Понятно.

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

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

Я в курсе, что это разные языки. Я так же в курсе, что на C++ можно писать как на C (за исключением некоторых очень специфичных штук типа structure initializer). А раз это можно делать, то этим люди и занимаются because they can.


И вашем мире розовых пони существует супер-классный C++17-20 (он мне тоже нравится), но на котором можно безопасно писать только с соблюдением устных договоренностей ("Срочно пишем по CppCoreGuidelines"), а в моем мире существует махровый C++, дай бог C++11, который пахнет как гавно, выглядит как говно, на вкус как говно. Его практически невозможно отличить от C, но типа в .cpp файликах, ага.

Мыши плакали, затыкали нос, но..?

Но поддерживали легаси за зп.

Простите, где подтверждения хотя бы каких-либо ваших утверждений?

По поводу современного C++ и CppCoreGuidelines, то могу вам сказать, что примитивные шаблоны вроде not_null или bounded_value, как и смарт-поинтеры и прочие вещи, облегчающие RAII, нормальные разработчики использовать стали задолго до. Где-то даже до принятия C++98 (шаблоны, если вы не в курсе, более-менее массово доступны стали с 1994-1995-х годов). Книги вроде Modern C++ Design и C++ Coding Standards — 101 Rules Guidelines — это 2001-й и 2004-й годы. По мерками ИТ совсем давно.

Говнокод можно наплодить везде. Счастье Rust-а в том, что до него еще говнокодеры из C++, Java и JavaScript-а не добрались. Как доберутся в массовых количествах, так запаритесь unsafe расчищать.

Счастье Rust'а в том, что на нем написать нормальный код проще, нежели наговнокодить.


Но так-то да. Скоро набегут великие "оптимизаторы" из C++, и придется расчищать и опять нюхать.

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

Но я тоже жду, пока «оптимизаторы» из C++ перебегут к вам.

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

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

в листинге на 1k строк 26 unsafe, в которых спрятана примерно половина кода.

Да-да, мы это уже проходили. Ищите другой аргумент, чем плох Rust.

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

Rust бы вам магическим образом ничем бы не помог.

Я объяснил, как именно он бы мне магически помог.

Я объяснил, как именно он бы мне магически помог.
Ошибку Rust бы вам предотвратил? Нет.

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

У меня лет 20 назад был похожий случай. В коде по десериализации двоичных данных было что-то вроде:
float parser::get_float() {
  const char * ptr = m_current_ptr;
  m_current_ptr += 4;
  return *((const float *)ptr);
}

На x86 работало нормально. На SPARK-е сразу же SIGBUS. И нашлось без проблем.
Все остальное — это спекуляции на тему того, как быстро конкретный человек в конкретном коде бы нашел проблему.

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


И поэтому я полез в код GC на Rust: withoutboats/shifgrethor, (начало цикла статей).


Вы знаете, код проще читать, тесты проще писать, баги проще искать. Ночью набил пару PR.

код проще читать, тесты проще писать,
Кому-то код на Lisp-е проще читать, чем код на Pascal.
баги проще искать.

Ну надо же, баги. Казалось бы, откуда им взяться, раз и Rust такой весь из себя, и говнокодеры из C++ еще не подтянулись. Неувязочка ;)
C, все-таки, существенно проще и в него лучше «входят» расширения для поддержки аппаратных/программных «выкрутасов» (типа TR 18037). ИМХО, в определенной сфере применения он удобнее C++, в котором герберизм-саттеризм не дает заниматься делом))).
Возможно. Разработчики аппаратуры наверняка входят в C проще.
Но это уже совсем офтопик.
Ну, думаю, что тут главное то, что C для работы на «низком» уровне выразительности хватает, а вот абстракций и концепций, требующих нетривиального рантайма нет. «C++ как улучшенный C» это, ИМХО, преувеличение. Помнится, в первом издании «Языка программирования C++» в финальном примере Страутсруп реализовал драйвер вывода на экран на C, чтобы подчеркнуть, как он писал, «разделение зоны ответсвенности» между этими языками. Как-то так, мне нравиться писать и на C, и на C++.
Вот ты разработчик, у тебя падает код в 60KLOC, который 5 лет «правильно работал». Что ты будешь делать? Я бы заплакал.

boost::stacktrace, google breakpad, в с++20 едет std::stacktrace. Ошибка локализуется еще на этапе чтения логов, до открытия IDE. Да и в целом краши — самые легко отлаживаемые баги. А если вдруг у вас сервер вернул «3» вместо «14», где «светло»?

И наш сервер не падает в рандомных местах. Не утекает, не ломается.

Во-первых, и на плюсах де-факто пишутся безопасные программы. И это не зависит ни от моего, ни от вашего, ни от чьего-бы-то-ни-было еще мнения по поводу плюсов/раста. Во-вторых, я, помнится, критиковал то, что вы сравниваете раст 2015-ого года с с++03. Сейчас вы пошли даже дальше, и сравниваете раст с вообще другим языком, си

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

Согласен. Безопасные пишутся. В 0.1%. А в 99.9% на плюсах пишутся небезопасные программы. И это не зависит ни от моего, ни от вашего, ни от чьего-бы-то-ни-было еще мнения по поводу плюсов/раста.


boost::stacktrace, google breakpad, в с++20 едет std::stacktrace.

А как это бы помогло отдебажить ошибку внутри C++ функции, которую вызывал JIT код, собранный самодельной VM, которая проявлялась только на g++ -O3 -march=native с кучей проходов LLVM?

внутри C++ функции, которую вызывал JIT код, собранный самодельной VM, которая проявлялась только на g++ -O3 -march=native с кучей проходов LLVM?
Так что есть в Rust для предотвращения этой ошибки? Вы поместите такой же for внутрь unsafe блока и получите те же проблемы.
Безопасные пишутся. В 0.1%. А в 99.9% на плюсах пишутся небезопасные программы

статистику в студию.

А как это бы помогло отдебажить ошибку внутри C++ функции, которую вызывал JIT код, собранный самодельной VM, которая проявлялась только на g++ -O3 -march=native с кучей проходов LLVM?

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

Он был правильно сгенерирован. Я спрашиваю каким образом можно отследить фреймы и распечатать stacktrace, если его нет или он покорёжен неопределенным поведением?

каким образом можно отследить фреймы и распечатать stacktrace, если его нет

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

или он покорёжен неопределенным поведением

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

При чём тут она? В слове «stacktrace» «stack» присутствует не просто так, и он на запись не лочится никогда.


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


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

НЛО прилетело и опубликовало эту надпись здесь
а с каким количеством программ полностью на с++11 и выше вы имели дело?
upd. имеются в виду программы, написанные в соответствии с практиками современного с++, а не с++03 код с другим флагом компилятора
Да давайте сделаем проще. Существование доказать сильно проще, чем универсальность. Покажите достаточно большую программу на С++, в которой нет UB.

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

но вы сами сказали, что существование доказать сильно проще. 0xd34df00d утверждал что все с++ проекты, с которыми он имел дело, имели UB. Я попросил лишь назвать число таковых, на современном с++. Вы сами прекрасно понимаете, что выкладывать примеры коммерческого софта (с которым я имел дело) я не могу.

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

Вы говорите о локальном использовании implementation-defined поведения, но забываете, что раст не стандартизован вообще и в нем всего один компилятор. В терминологии с++, поведение раста на 100% «implementation-defined».

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


Вы говорите о локальном использовании implementation-defined поведения

Речь идёт о нестандартных расширениях GCC -fwrapv, -fno-strict-aliasing и др. Согласно стандарту соответствующие конструкции вызывают undefined behavior. Так что ядро линукса — нестандартный C и до недавнего времени могло компилироваться только с помощью GCC.


В терминологии с++, поведение раста на 100% «implementation-defined».

Есть The Rust Reference и там не написано, что всё поведение — implementation defined. Отсутствие стандарта не означает, что ничего не определено. Кстати, C был стандартизирован через 17 лет после появления — 29 лет назад, а ядро линукса написано на нестандартном C. В общем, не стоит преувеличивать ценность стандартизации.

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

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

Согласно стандарту соответствующие конструкции вызывают undefined behavior. Так что ядро линукса — нестандартный C и до недавнего времени могло компилироваться только с помощью GCC.

«undefined behavior» значит «поведение, не определенное стандартом», а не «ошибочное поведение». Компилятор имеет право корректно обрабатывать UB не нарушая при этом стандарт. Программа при этом, разумеется, становится не стандартной, а привязанной к конкретному компилятору. Тот же статус у всех rust программ

Есть The Rust Reference и там не написано, что всё поведение — implementation defined

но rust reference не первичен, это просто описание текущего поведения компилятора. При изменении компилятора меняется rust reference, а не наоборот.
> Компилятор имеет право корректно обрабатывать UB не нарушая при этом стандарт

Зачем тогда эти ключи в GCC? Да и применимость определения «корректная обработка» в случае UB вызывает сомнения. Корректная согласно здравому смыслу? У комитета по стандартизации не достаёт здравого смысла? Или что-то другое?

> При изменении компилятора меняется rust reference, а не наоборот.

Почему? Может удобнее? Как с C++ делают: обкатывают экспериментальные реализации фич в компиляторах, а потом вносят в стандарт. А то иногда нехорошо получается, как с extern template. Кстати, дополнительная реализация Rust уже есть: mrustc.
Да и применимость определения «корректная обработка» в случае UB вызывает сомнения.

Корректность ПО — его способность реализовывать требуемый алгоритм в определенных условиях. Ни больше, ни меньше. Полагаться на документированное поведение компилятора — ваше право (так же, как и в расте). Стандарт лишь описывает требования к компиляторам.

Как с C++ делают: обкатывают экспериментальные реализации фич в компиляторах, а потом вносят в стандарт

при этом чаще всего в стандарт попадает не версия из компилятора, а измененная/исправленная комитетом.
Разница огромна, и она в следующем: если вдруг какой-то метод в расте будет вести себя вопреки конкретно вашим ожиданиям, нет ни одного документа, с помощью которого можно было бы ответить на вопрос «это баг или фича (или implementation defined)?».

Кстати, дополнительная реализация Rust уже есть: mrustc.

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

Думаю обсудят и решат, где ошибка. Если, что-то описанное в reference вызывает unsoundness в системе типов, поправят reference. Если программа скомпилированная mrustc/rustc не реализует требуемый алгоритм в условиях, описанных в reference, поправят компилятор. Если расходится поведение не описанное в reference, доопределят reference и поправят соответствующий компилятор или поправят и доопределят. Если mrustc не компилирует программу из-за того, что в rustc добавили новую фичу (о чём можно узнать у разработчиков языка, или, если хочется иметь текст, попросить их внести это в reference) — очевидно, что нужно добавить новую фичу в mrustc и reference.

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

По сути «если у 2-х из 3-х одинаково, то последний неправ». А если везде (reference/rustc/mrustc) по-разному? А если всё-таки неправы двое?

так что Rust потенциально может обогнать C по срокам формальной стандартизации.

вы спорите со мной сегодня. На сегодняшний день у раста стандарта нет.

Но речь не совсем об этом. А о том, что весь rust код на данный момент implementation-defined. И в этом, на мой взгляд, нет ничего ужасного. Я лишь указываю на иронию ситуации, в которой фанаты раста критикуют c++ за использования поведения, не определенного стандартом
По сути «если у 2-х из 3-х одинаково, то последний неправ». А если везде (reference/rustc/mrustc) по-разному? А если всё-таки неправы двое?

Сделают то же самое, что делают во всех языках при обнаружении дыр в стандарте — доопределят стандарт (в данном случае неформальный). Вам обязательно печать IEEE/ISO/ANSI нужна?


Я лишь указываю на иронию ситуации, в которой фанаты раста критикуют c++ за использования поведения, не определенного стандартом

Да нет никакого тотального implementation defined в смысле C++. Есть неформальный стандарт, частью описанный в reference, частью описанный кодом rustc. В reference явно указывается, что implementation defined, а что — нет, и что — undefined behavior.


Кроме того, никто С++ за implementation defined не ругает. Ругают за кучу возможностей получить undefined behavior в любом месте программы без явного обозначения этих мест.

Документация раста генерируется напрямую из комментариев в коде. Поэтому нет никакого «неформального стандарта», есть лишь описание текущей реализации.

Кроме того, никто С++ за implementation defined не ругает.

хм

Ругают за кучу возможностей получить undefined behavior в любом месте программы без явного обозначения этих мест.

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

Я говорил про The Rust Reference. Она из кода не генерируется.


хм

Нестандартное расширение компилятора и implementation defined — разные вещи. Если бы в стандарте было написано, что знаковое переполнение — implementation defined, то не понадобился бы ключ -fwrapw, который переводит компилятор в нестандартный режим, в котором знаковое переполнение определено как two's complement.


надо сначала определить поведение. Чуете иронию?

Нет, не чую. Стандарты не снисходят с небес, а проходят процесс развития. Rust сейчас в стадии, когда рано его формально стандартизировать. Но это не значит что программа на Rust'е может делать всё что угодно, так как в The Rust Reference не написано, что, скажем, глубина анализа при выведении типов — implementation defined.


Формальная стандартизация — не магический ритуал, делающий язык лучше. Это индикатор того, что язык достиг определённой степени зрелости. Поэтому я и упоминаю постоянно 17 лет нестандартного С, чтобы напомнить что ничего особенного тут нет.

implementation-defined отличается от undefined behavior только тем, что первое компилятор обязан как-нибудь реализовать, а второе — нет. Однако это не значит, что компилятор обязан оставить поведение неопределенным. Например, «положить в union значение одного типа а достать — другого» это UB по букве стандарта. Однако, такое поведение определено во всех основных компиляторах одинаково и повсеместно используется.

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

так как в The Rust Reference не написано, что, скажем, глубина анализа при выведении типов — implementation defined.

rust reference вообще никакое поведение не определяет, а лишь описывает. И вообще ни один документ поведение компилятора раста не определяет

Формальная стандартизация — не магический ритуал, делающий язык лучше

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

Да ладно отмазываться. Я как Rust разработчик, страдаю от отсутствия стандарта. Приходится порождать вопросы типа таких: https://github.com/rust-lang-nursery/reference/issues/485

Отмазываться? От чего? Штампика ISO на The Rust Reference нет, он неполон, но говорить, что в Rust'e всё — implementation defined неправильно.

> порождать вопросы типа таких

Хех. Я был бы очень удивлён, если бы Rust исправлял UB в C-библиотеке.

Хм, если бы Rust менял правила работы с i8 в зависимости от того из какого языка вызывается библиотека, это было бы очень странно, если возможно.


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

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Отвечаю сразу на 1, 2, 3, 4.

Погодите, еще раз. Вот у нас ситуация: «программист на языке A полагается не на стандарт, а на поведение конкретного компилятора, описанное в документации к этому компилятору».

Если A = «раст», то под неё подходит абсолютно весь код, и вы считаете, что это нормально.

Но если вдруг A = «c++», то вы орете как это плохо, «UB», «это даже не с++», «плохая распространенная ментальность общества» и прочее. И это только потому, что в с++ (в отличие от раста) есть настоящий документ, по-настоящему описывающий его поведение? На мой взгляд, вы сейчас демонстрируете запредельное лицемерие. И вам за него еще лайки ставят. Я в шоке
НЛО прилетело и опубликовало эту надпись здесь

Вы не понимаете, что есть поведение "стандартное" (не важно, чем оно определено: действительным формальным стандартом или даже просто договоренностью) и "нестандартное", то есть известное ошибочное поведение.


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


В Rust есть явная, четкая граница между этими двумя мирами, в C++ — такой нет.


Если, например, gcc или другой компилятор несколько уточняет стандарт C++, то есть выдает дополнительные гарантии там, где стандарт языка от них отказывается — если такие дополнительные гарании компилятор действительно дает, а не просто мы полагаемся на текущую его реализацию — то да, корректнее будет сравнивать тогда не C++ vs. Rust, а GCC C++ vs. Rust (оставляем за скобками тот момент, что человек может писать на GCC C++ при этом думая, что он пишет на C++, и связанные с этим проблемы в будущем).


Но меняет ли это ситуацию принципиально? Является ли GCC C++ — действительно безопасной версией C++, лишенной UB или явно ограничивающей его от остального кода? Только это обстоятельство принципиально может отличить безопасность GCC C++ от C++ как такового.

Вы не понимаете, что есть поведение «стандартное» и «нестандартное», то есть известное ошибочное поведение.

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

не важно, чем оно определено: действительным формальным стандартом или даже просто договоренностью

Вот решил я, например, сделать rust компилятор. Распечатайте мне, пожалуйста, эту устную договоренность.

то да, корректнее будет сравнивать тогда не C++ vs. Rust, а GCC C++ vs. Rust

Вы хотели сказать GCC C++ vs. Mozilla Rust?

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

С точки зрения языка, «реализация» и «компилятор» — синонимы. Да, компилятор берется гарантировать некоторые инварианты при использовании этого компилятора. Но не более

Но меняет ли это ситуацию принципиально?

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

Является ли GCC C++ — действительно безопасной версией C++, лишенной UB или явно ограничивающей его от остального кода?

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

Где вы вычитали такое? Сами придумали, сами разоблачили :)


В Rust есть безопасное подмножество, которое практически не содержит UB. В С++ вы можете иметь такие же гарантии для произвольного участка кода?

safe rust вообще не содержит UB. В сейф расте может засегфолтится что-то, но оно будет вызвано UB ранее по флоу в unsafe сегменте.
вы хотя бы прочитайте что такое undefined behavior прежде, чем использовать этот термин
Да нет, смотрю вот в доку, и там всё написано.

там правда есть сноска, что
The following list is not exhaustive

Но пока никто не сталкивался с UB, которого бы не было в этом списке.
Это список поведения, которое гарантированно не определено в конкретном компиляторе. Давайте сформулируем так: я хочу быть уверен, что корректно работающая на одном компиляторе программа соберется и будет корректной на любом другом компиляторе. Подмножество языка, для которого это будет выполняться, описано стандартом для с++ и не существует у раста. И именно это подмножество называется «определенным» поведением. Всё, что за пределами этого подмножества — «неопределенное поведение».

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

Поэтому спор не сводится к наличию/отсутствию формально описанных случаев UB в стандарте языка, а к наличию/отсутствию UB в языке по-факту.

вы даже не поняли предмет спора. Я говорил о том, что UB в с++ вы почему-то оцениваете по букве стандарта, а в расте — по букве документации к компилятору. Соответственно многие виды поведения, определенного компилятором с++ но не определенного стандартом вы причисляете к UB.
  • ...
  • Вот когда раст по IEEE стандартизуют, тогда и поговорим <- вы здесь
  • ...

Серьезно, вам выше дали линк на документацию, где написано, что такое УБ. От того, что там нет шильдика "IEEE" она не стала бесполезной. Вам уже подсказали, что есть другие реализации.


И да, стандарт рано или поздно появится, думаю, в ближайшие пару лет. А вот ситуация со "случайными" уб в плюсах — нет.


Как писал товарищ выше, в любой достаточно сложной программе на С++ есть возможность проексплуатировать UB в клиентском коде, и всё держится на том, что вызывающий код обещает не нарушать гарантий. Что, кстати, подводит нас к мысли, что С++ кода не существует.




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


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

пять человек не могут понять что я имею в виду после более чем 10 постов с пояснениями. Ужасно.

Еще раз: сравнивая с++ и раст вы даете огромную фору расту потому, что с++ оцениваете с точки зрения стандарта, а раст — с точки зрения поведения компилятора. У любого конкретного компилятора с++ многие виды UB определены. Программа может их содержать но при этом быть корректной для целевого окружения. Про такой код можно сказать, что он содержит UB, но это если оценивать его по стандарту. А вот программу на расте оценить по стандарту невозможно. Я лишь указываю на то, что проводить параллели между конкретной реализацией раста и стандартным с++ нельзя.

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

… и вот когда он появится, вдруг окажется, что у компилятора есть и расширения, и гарантии поверх стандарта, флаги типа "-no-standard-rule" и пр. Всё то самое, за что тут критикуют плюсы.

Серьезно, вам выше дали линк на документацию, где написано, что такое УБ. От того, что там нет шильдика «IEEE» она не стала бесполезной. Вам уже подсказали, что есть другие реализации.

Эта документация даже не претендует на полноту описания даже конкретной реализации:
For now, this reference is a best-effort document. We strive for validity and completeness, but are not yet there. In the future, the docs and lang teams will work together to figure out how best to do this. Until then, this is a best-effort attempt. If you find something wrong or missing, file an issue or send in a pull request.
А вы пытаетесь меня убедить что она описывает в том числе и другие компиляторы. То, что в одном компиляторе — UB, в другом может быть определено и наоборот. Подмножество гарантированно определенного во всех компиляторах поведения нигде не описано. Всё, что вне этого подмножества, формально является «неопределенным поведением».
Еще раз: сравнивая с++ и раст вы даете огромную фору расту потому, что с++ оцениваете с точки зрения стандарта, а раст — с точки зрения поведения компилятора. У любого конкретного компилятора с++ многие виды UB определены.

Если вы под компилятором имеете ввиду семейство, например gcc, давайте сравнивать на нем.


… и вот когда он появится, вдруг окажется, что у компилятора есть и расширения, и гарантии поверх стандарта, флаги типа "-no-standard-rule" и пр. Всё то самое, за что тут критикуют плюсы.

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


А вы пытаетесь меня убедить что она описывает в том числе и другие компиляторы. То, что в одном компиляторе — UB, в другом может быть определено и наоборот. Подмножество гарантированно определенного во всех компиляторах поведения нигде не описано. Всё, что вне этого подмножества, формально является «неопределенным поведением».

Верно.


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


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


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

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

вот именно за это я вас с самого начала и критикую

То, что это формально не специфицировано — неприятно, конечно, но практически никаких проблем с этим не бывает.

вот это часто верно для с++.
вот именно за это я вас с самого начала и критикую

Ок, сойдемся на этом

Из того, что отсутствие некоторых UB С++ гарантируется тем или иным компилятором, ситуация принципиально не меняется. По-прежнему C++ допускает UB на любом копиляторе, а safe Rust — нет. Вам об этом уже не один раз сказали.

Вам уже подсказали, что есть другие реализации.
«Есть» и «другие» — это несколько преувеличено. Поскольку упомянутый до сих пор mrustc, мягко говоря, сыроват: mrustc works by compiling assumed-valid rust code (i.e. without borrow checking)
Да, формирование указателя за пределы элемента-следующего-за-последним-элементом-массива — UB, даже если вы его не разыменовываете.

А вот и нет:)

First of all, it is not allowed to perform pointer arithmetic (like &x[i] does) that goes beyond either end of the array it started in. Our program violates this rule: x[i] is outside of x, so this is undefined behavior. To be clear: Just the computation of x_ptr is already UB, we don’t even get to the part where we want to use this pointer!1

But we are not done yet: This rule has a special exception that we can exploit to our advantage. If the arithmetic ends up computing a pointer just past the end of an allocation, that computation is fine. (This exception is necessary to permit computing vec.end() for the usual kind of C++98 iterator loop.)

Есть правда нюанс

This seems to break the optimization. However, the C++ standard has another trick up its sleeve to help compiler writers: It doesn’t actually allow us to use our x_ptr above. According to what the standard says about addition on pointers, x_ptr points “one past the last element of the array object”. It does not point at an actual element of another object even if they have the same address. (At least, that is the common interpretation of the standard based on which LLVM optimizes this code.)


Взято отсюда
НЛО прилетело и опубликовало эту надпись здесь
Хм, чет тупанул да.
но вы сами сказали, что существование доказать сильно проще. 0xd34df00d утверждал что все с++ проекты, с которыми он имел дело, имели UB. Я попросил лишь назвать число таковых, на современном с++. Вы сами прекрасно понимаете, что выкладывать примеры коммерческого софта (с которым я имел дело) я не могу.

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

Вы говорите о локальном использовании implementation-defined поведения, но забываете, что раст не стандартизован вообще и в нем всего один компилятор. В терминологии с++, поведение раста на 100% «implementation-defined».

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

С другой, формализованный IEEE стандарт, конечно же, хотелось бы видеть.
а если я назову libc++, вас это устроит? )
НЛО прилетело и опубликовало эту надпись здесь
я уже неоднократно писал, что сравнивать устаревшие плюсы с новым растом некорректно. Почему вы стремитесь к современным плюсам приписать пласт вплоть до ANSI C?
НЛО прилетело и опубликовало эту надпись здесь
Вы критикуете обратную совместимость, aka основное достоинство плюсов?

Теоретическая возможность использовать устаревшие языковые конструкции не влечет повсеместное злоупотребление ими. Вам же не надо ежесекундно говорить «не суй пальцы в разетку»?
НЛО прилетело и опубликовало эту надпись здесь
Погодите. А как похмелье, невнимательность или джун (который начинает уже с современных плюсов и старых техник не знает) заставят вдруг деградировать, например, с алгоритмов на циклы? Реально проблема может быть только в привычке, но во-первых, говнокодить можно на любом языке, а во-вторых, раст еще слишком молод чтобы иметь говнокодеров старой гвардии.

В конце концов, никто не застрахован от лэгаси, и не говорите мне что его нет в плюсах

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

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

Вот например с позиции «на каком языке начинать новый проект» легаси вообще не имеет значения. Раст — хороший выбор, спору нет. Но и новые плюсы достойны. Особенно учитывая, что на них вероятность наткнуться на недостающие библиотеки намного ниже.
НЛО прилетело и опубликовало эту надпись здесь
Для всех программ, с которыми я работал и которые были сложнее пары сот строк, компилятор рано или поздно утыкался в UB.
Да ладно, дался вам этот UB. Наличие UB в коде еще не означает наличие бага. Вот, скажем, такая функция:
int avg(const vector<int> & data) {
  return accumulate(begin(data), end(data), 0) / data.size();
}
Она же не только UB содержит (переполнение при знаковом целочисленном сложении), но и еще ошибку (ее нельзя использовать, если data пустой). Тем не менее, будет ли означать использование этой функции в программе обязательное наличие ошибки?

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

Ну и да, в Rust-е ничего не препятствует написанию такого же avg и, если программист ошибся в своих предположениях и задействовал такую avg на неподходящих данных, он получит проблемы в run-time. Да, возможно, диагностировать причину сбоя будет проще. Только вот суть в том, что сбой не был предотвращен.

Или другой пример, приснопамятный двусвязный список:
template<typename T>
class list_item {
  T value_;
  list_item<T> * prev_;
  list_item<T> * next_;
public:
  list_item(T && v) : value_{move(v)} {} // prev_ и next_ не инициализированы!

  void bind(list_item<T> * prev, list_item<T> * next) {
    prev_ = prev; next_ = next;
  }
  list_item<T>* prev() const { return prev_; }
  list_item<T>* next() const { return next_; }
};

Тут работа с методами prev() и next() прямым ходом ведет к UB, т.к. если для нового объекта list_item не был вызван bind(), то возвращаться будет мусор, так что даже такой «типа защищенный» код:
list_item<T> * prev = item.prev();
if(prev) prev->bind(...);
никакой защиты обеспечивать не будет.

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

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

a) формально UB присутствует практически в любой C++ программе, вне зависимости от ее размера и сложности. Есть операции над целыми числами со знаком — получите UB. Есть обращения по указателям — опять UB;

b) наличие формальных UB в коде не ведет автоматически к наличию багов в коде.

Контракты — это вообще отдельная тема для разговора.
Формально, UB присутствует там, где невозможно доказать его отсутствие. Есть явная проверка, что переполнения при знаковом сложении не будет? Окей — значит, и UB не будет (пример сознательно упрощённый — понятно, что в реальных случаях это будет сделано не так «в лоб»).
Простите, мне не интересно заниматься софистикой.

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

ну, это если совсем формально. Например, если формально, в ссылку можно передать *(T*)nullptr и получить тот самый UB. Более того, компилятор скорее всего даже выкинет проверку ссылки против nullptr, если её туда добавить. Причем так можно сделать и в расте — передать сгенерированную через unsafe ссылку на null в safe код. И там, уже в safe коде, проявится UB.
Она же не только UB содержит (переполнение при знаковом целочисленном сложении), но и еще ошибку (ее нельзя использовать, если data пустой). Тем не менее, будет ли означать использование этой функции в программе обязательное наличие ошибки?

Да, будет означать.
Да, будет означать.
ППЦ какой-то. Так толсто, что даже тонко.
Да нет, я серьезно. Если компилятор вместо вызова функции вставит запуск ханойских башен, вас тоже такой результат устроит, верно?
Да нет, я серьезно
Простите, но нет, серьезно воспринимать вас и ваши доводы я уже не могу. Любой идиотизм в желании разрекламировать свою любимую игрушку должен иметь свои пределы.
Дело не в игрушке. Код с UB — плохой, и его быть не должно. Потом вызываются никогда не вызываемые функции форматирования диска, вместо полезной нагрузки сервак запускает игрушки, и вот это всё.

Если для вас это норм, то я даже и не знаю…
Код с UB — плохой, и его быть не должно.

Имеем код:
int sum(int a, int b) { return a + b; }
Это код с UB. И вот в таком случае UB, действительно, происходит:
int s = sum(numeric_limits<int>::max(), numeric_limits<int>::max());
А вот в таком, уже нет:
int a = sum(some_value%100, another_value%100);

Код sum один и тот же, в нем UB присутствует. Но вот использование sum может как приводить к появлению UB, так и не приводить.

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

А это уже ППЦ и прямое указание, что вам следует программировать на Rust-е, т.к. в языках, которые за вами сопли не подтирают, вы отстрелите ноги не только себе. Вы это умудряетесь делать даже в C#.

Так что простите, но воспринимать вас всерьез уже невозможно.
Код sum один и тот же, в нем UB присутствует. Но вот использование sum может как приводить к появлению UB, так и не приводить.

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


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

Наличие UB означает, что можно легко отстрелить ногу, и всё. Если джун написал int s = unknown_func(numeric_limits<int>::max(), numeric_limits<int>::max());, виноват он, или лид, который написал такую unknown_func, и не вставил проверки инвариантов? И вне зависимости от того, кто виноват, придется потом тратить кучу времени на поиск того, почему иногда софтина падает.

Нет, а серьёзно. Вызов этой функции является UB, на Ваш взгляд? Если да — то в чём здесь «ППЦ»? Если нет — то чем она принципиально отличается от оператора знакового сложения, кроме того, что не прописана в стандарте?
Вызов этой функции является UB, на Ваш взгляд?
Пожалуйста, перечитайте внимательно, то, что было написано. В коде есть UB, в коде есть ошибка. Но использование avg не обязательно будет приводить хоть к каким-либо проблемам.

ППЦ — это утверждение, что будет приводить обязательно.

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

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

Вызов этого кода не обязательно приводит к UB. Показанные выше примеры с функциями avg и sum тому подтверждение.

Человек, который использует библиотечную функцию, должен быть уверен, что всё будет хорошо. Функция выглядела бы обычно, если бы у нее были 2 версии:


unsafe int avg(const vector<int> & data) {
  return accumulate(begin(data), end(data), 0) / data.size();
}

checked_int avg(const vector<int> & data) {
  if (data.empty()) {
    exit_or_panic();
  }
  checked_int acc = 0; // checked_int panics on overflow
  return accumulate(begin(data), end(data), acc) / data.size();
}

И пользователю давали выбор:


  • по-умолчанию функция будет безопасна, проверит размер массива, паникует при переполнении (или бросает ошибку, или возвращает ошибку в стиле Rust/Go)
  • небезопасный вариант, когда ты 100% уверен во входящих данных, но если подсовываешь во входные данные, на выходе получаешь UB

Я доступно объяснил?

if (std::distance(begin(data), end(data)) == 0)
Теперь понятно, откуда берется говнокод на C++, о котором вы постоянно говорите.

:D да, тупанул. Позор мне.


Тем не менее эта помарочка все, что вы можете сказать? Никакого прозрения не вызывает? Или так же в голове сплошная безнадега: "Раст плохой, потому что я же учил C++ 10 лет, я не могу выкидывать свой устоявшийся подход к решению проблем на помойку"?

Тем не менее эта помарочка все, что вы можете сказать?
Все, что я хотел сказать, я уже сказал, а именно: сторонники Rust-а автоматически приравнивают UB, существующие в C++ном коде, к обязательно и неизбежно возникающим багам. Что вовсе не так.
«Раст плохой, потому что я же учил C++ 10 лет, я не могу выкидывать свой устоявшийся подход к решению проблем на помойку»?
Однажды я уже попросил привести цитату из данного обсуждения, в которой я бы называл Rust плохим. Вы не смогли этого сделать (как не смогли подтвердить и другие свои выпады). Могу попросить еще, но результат будет таким же.

И суть вовсе не в том, что я судорожно держусь за C++ из-за страха потерять накопленный опыт. А в том, чтобы разговаривая про технологии разговор шел предметный и объективный. Со стороны упоротых растоманов, так уж получается, этого никогда не происходит. Сплошные передергивания и голословные утверждения, а иногда и явные проблемы с логикой.

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

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


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

Да-да, все мы читали ваш бложик, где вы с радостью поносили Rust. Прям вершина объективной критики, ага.

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

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

Какие-то еще примеры «поношения Rust-а» можете привести?

Пожалуй, нет

Все, что я хотел сказать, я уже сказал, а именно: сторонники Rust-а автоматически приравнивают UB, существующие в C++ном коде, к обязательно и неизбежно возникающим багам. Что вовсе не так.

Спасибо, Кэп. Более того я скажу, само возникновение UB — формально, не обязательно приводит к багам (ибо оно "неопределенное"). Проблема, о которой говорят вами ненавистные сторонники Rust-а — это то, что баги в таком коде могут появиться легко и незаметно. Вы с этим спорите? Если нет, то вопрос закрыт. А читать ваши сетования на сторонников Rust-а с выдуманными же вами взглядами — как-то совсем кисло.

Кэп, вас никто не заставляет читать мои сетования.

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


А если сказать так: «к багам, которые могут проявиться при любом изменении окружения»? Если программа скомпилирована один раз — конечно, в ней баг либо уже есть, либо и не будет (ну, если, конечно, не сломается что-то в системных вызовах). Но UB на то и UB, что любое изменение условий компиляции, вплоть до минорной версии любого звена, может его изменить. В том числе и превратить его в баг. Такой вариант звучит более разумно?

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


Знаковое целочисленное сложение. Функция int sum(int a, int b) { return a+b; } содержит UB, но не эксплуатирует его. А вот какой-нибудь Вася Пупкин может эксплуатировать этот UB исходя из предположения, что на его платформе переполнение int-а ведет себя так же, как и переполнение unsigned int. Что, естественно, может когда-нибудь выйти боком, если код Васи Пупкина запустят на какой-нибудь экзотической платформе, где целочисленное переполнение приводит к аппаратному прерыванию.


А вот код Пети Иванова, в котором используется тот же sum, не будет эксплуатировать UB. Например, потому, что входные данные для sum жестко проверяются вычислительным алгоритмом.


Соответственно, в sum UB присутствует, это может эксплуатировать и может приводить к проблемам. А может и не эксплуатироваться и проблем не будет вне зависимости от среды исполнения и версий компилятора.


Другой пример, знаменитое удаление файлов из-за обращения по нулевому указателю: https://gcc.godbolt.org/z/dFk_fk
Проблема здесь возникает из-за эксплуатации UB. И поведение при эксплуатации UB, как раз и будет зависеть от среды и от компилятора.


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


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


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


А вот эксплуатация UB — это прямой путь к проблемам.


Собственно, поэтому можно рассуждать о том, как часто в C++ коде эксплуатируются UB (сознательно или нет), насколько просто непреднамеренно задействовать UB и т.д.


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

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

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

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

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

Грубо говоря, вы хотите, чтобы обращение по нулевому указателю всегда было определено и всегда приводило к segfault-у.

Однако, изначальная проблема в том, что что программа, в которой есть такое обращение, некорректна. И то, что она некорректна, нельзя исправить гарантировав segfault при обращении к nullptr.
Критикуя C++ ты критикуешь инструмент. Неважно, какой язык ты используешь, важно твое умение программировать.

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

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

Ну а то, что вы ссылаетесь на статью, где на полном серьезе говорится «Обратная совместимость с Си как маркетинговая фича» и под это еще и какая-то база подводится, больше говорит о вас и о вашем знании предмета.
И я в очередной раз обращаю внимание на то, что наличие UB в языке имеет лишь косвенное влияние на корректность кода, поскольку в корректном коде UB практически не эксплуатируется.


Ну давайте тогда придем к выводу, что в таком случае корректного кода на С++ кроме никто никогда не пишет, и закроем тему.

Ну а то, что вы ссылаетесь на статью, где на полном серьезе говорится «Обратная совместимость с Си как маркетинговая фича» и под это еще и какая-то база подводится, больше говорит о вас и о вашем знании предмета.

С++ изначально и был better C. То, что они затем разошлись, никак не повлияло на его изначальное позиционирование.

И да, это никак не отменяет цитаты выше.

P.S. до сих пор непонятно, почему мы про плюсы говорим бтв. Я же изначально говорил про высокоуровневые вещи и удобство разработки, а скатилось всё опять в «а вот в плюсах УБ». Даже если это (не) так, то разговор не об этом, и срачей про «rust vs C++» хватает. Хотел раз в жизни обсудить сравнение с котлином и шарпами, так и тут как-то разговор свернул не туда.
Ну давайте тогда придем к выводу, что в таком случае корректного кода на С++ кроме никто никогда не пишет, и закроем те
Явное противоречие с окружающей реальностью не смущает? Ну ОК.
С++ изначально и был better C.
Матчасть нужно учить.
Хотел раз в жизни обсудить сравнение с котлином и шарпами, так и тут как-то разговор свернул не туда.
Ну так сравнили бы в тексте статьи, тогда может и пошел бы разговор в нужное русло. А если статья просто о Rust-е, то противопоставление его с плюсами неизбежно, т.к. и тот и другой находятся в нише нативных языков без GC. И разработчикам на Kotlin или C# эта ниша мало интересна.
Явное противоречие с окружающей реальностью не смущает? Ну ОК.

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

Матчасть нужно учить.

Ну да, C with Classes задумывался как совершенно другой язык, а совместимость с кодом на С… ну, случайно получилось.

Ну так сравнили бы в тексте статьи, тогда может и пошел бы разговор в нужное русло. А если статья просто о Rust-е, то противопоставление его с плюсами неизбежно, т.к. и тот и другой находятся в нише нативных языков без GC. И разработчикам на Kotlin или C# эта ниша мало интересна.

То, что у него нет GC в контексте статьи нам не интересно.
Реальность про то
Реальность — она про то, что большинство комментариев в этой теме, наверняка, написана из браузеров, реализованных на C++. Но ведь корректных программ на C++ не бывает, ведь так?
Ну да, C with Classes задумывался как совершенно другой язык, а совместимость с кодом на С… ну, случайно получилось.
Матчасть поучите.
То, что у него нет GC в контексте статьи нам не интересно.
Если вы хотите сравнения с Kotlin/C#, то отсутствие GC — это крайне важно. И, по моему скромному мнению, ставит крест на использовании Rust-а для прикладной, не привязанной к системщине, разработке. Ибо мало что дает такой прирост продуктивности прикладного разработчика, как GC.

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

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

Матчасть поучите.

расскажите вашу точку зрения.

Если вы хотите сравнения с Kotlin/C#, то отсутствие GC — это крайне важно.

Я так не считаю, о чем и статья. GC дает примерно столько же преимуществ, сколько съедает. Главное — отсутствие ручного управления памятью, а не наличие гц.

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


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

он не будет парится с Rust-овскими лайфтайми, а воспользуется преимуществами, которые ему дает GC.

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

А LLVM постоянно мешает вам собирать ваш Rust-овый код, ведь LLVM на C++.
расскажите вашу точку зрения
Подробности хорошо изложены в «Дизайн и эволюция языка C++». В двух словах, Страуструпу нужен был язык Simula, но с производительностью языка C. И C был выбран для экономии сил, т.к. в случае с С не нужно было делать ни бэк-энд для компилятора, ни линкер, ни стандартную библиотеку, ни другие инструменты, которые уже существовали для C++.

Начало 1980-х — это совсем другое время, LLVM тогда не было.
Главное — отсутствие ручного управления памятью, а не наличие гц.
Так у вас выбор в принципе не большой: либо ручное управление (пусть даже с полуавтоматическими средствами, вроде подсчета ссылок), либо полностью автоматическое, т.е. GC. Вот в Rust-е GC по итогу не оказалось. Что дает ему серьезное конкурентное преимущество в одной нише. Но точно так же оказывается тяжелым гандикапом в другой нише.
В обычном прикладном коде никто не пишет свои графы и списки.
В мой фразе было ключевое слово «если». Вы этого не заметили, что не удивительно.
Кроме того, «отучаемся говорить за всех» (с)
Подробности хорошо изложены в «Дизайн и эволюция языка C++». В двух словах, Страуструпу нужен был язык Simula, но с производительностью языка C. И C был выбран для экономии сил, т.к. в случае с С не нужно было делать ни бэк-энд для компилятора, ни линкер, ни стандартную библиотеку, ни другие инструменты, которые уже существовали для C++.

Для чего он был выбран?

Так у вас выбор в принципе не большой: либо ручное управление (пусть даже с полуавтоматическими средствами, вроде подсчета ссылок), либо полностью автоматическое, т.е. GC

Это не правда. Ownership вполне можно рассматривать, как автоматическое управление без GC.

В мой фразе было ключевое слово «если». Вы этого не заметили, что не удивительно.

Ну *если*, нужно, то раст хуже подходит, не вопрос. С этим я нигде и не спорил.
Для чего он был выбран?
Повторю специально для вас:
C был выбран для экономии сил, т.к. в случае с С не нужно было делать ни бэк-энд для компилятора, ни линкер, ни стандартную библиотеку, ни другие инструменты, которые уже существовали для C++.
Но если и это вам не понятно, то тогда, специально для вас, пара-тройка аналогий из современного мира:

* LLVM был выбран разработчиками Rust-а вместо того, чтобы делать полностью свой компилятор с нуля;
* JVM был выбран для Scala, Gosu и Kotlin для того, чтобы не делать run-time языка с нуля;
* .NET для выбран для F# для того, чтобы не делать run-time языка с нуля (да и stdlib так же).

В начале 1980-х таких опций у разработчиков ЯП не было. Поэтому Страуструп вместо того, чтобы делать все с нуля, сделал лишь Cfront — транспилер из C++ в С, что позволило преиспользовать существующие компиляторы C, ликеры, профайлеры и пр. инструменты. А так же stdlib C. Ведь в изначальном C++ стандартная библиотека собственно языка C++ включала разве что iostreams. Даже STL-я тогда не было.
Это не правда. Ownership вполне можно рассматривать, как автоматическое управление без GC.
Я уже понял, что всерьез вас воспринимать нельзя и вам еще (в лучшем случае) многому нужно учиться. Но все-таки читайте внимательно. У меня было ключевое слово: полностью автоматическое управление.

Лакмусовая бумажка — это способность механизма управления памятью справляться с циклическими ссылками. Если механизм не может делать это корректно без участия разработчика, то это не GC. Вот в Rust-е не GC.

А в Java, C#, Kotlin, Go, Eiffel, D и куче других языков с GC, циклические ссылки разруливаются автоматически. Разработчику нет надобности прибегать к использованию weak-references или чего-нибудь еще.
Ну *если*, нужно, то раст хуже подходит, не вопрос. С этим я нигде и не спорил.
Тем не менее, вы хотите сравнивать Rust с Kotlin/C#. В этих языках полноценный GC, в Kotlin к тому же еще и null safety, насколько я помню, присутствует. Поэтому в плане прикладного программирования Rust мало кому интересен, в отличии от Kotlin-а.
Повторю специально для вас:

Я спрашиваю, не почему был выбран именно С, а для чего он был выбран? Чтоыб огурцы солить? Чтобы посмотреть на него и выкинуть? Чтобы сделать надмножество этого самого языка, переиспользовав инфраструктуру?

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

В моем прикладном коде Rc/… не используется вообще. Там полностью автоматическое управление. Что теперь?

Лакмусовая бумажка — это способность механизма управления памятью справляться с циклическими ссылками. Если механизм не может делать это корректно без участия разработчика, то это не GC. Вот в Rust-е не GC.

В большинстве программ нет никаких циклических ссылок. Особенно это касается типовых MVC приложений.

Тем не менее, вы хотите сравнивать Rust с Kotlin/C#. В этих языках полноценный GC, в Kotlin к тому же еще и null safety, насколько я помню, присутствует. Поэтому в плане прикладного программирования Rust мало кому интересен, в отличии от Kotlin-а.

Ну мало интересен. В тех 1% задач, где этот минус проявляется.
а для чего он был выбран?
У меня не хватит сил разжевать вам простые вещи еще более примитивным образом. Все «зачем» и «для чего» уже были описаны выше, сосредоточтесь, пожалуйста, и пречитайте.
В моем прикладном коде Rc/… не используется вообще. Там полностью автоматическое управление. Что теперь?
Наверное, мне пора понять, что объяснить вам базовые вещи у меня не получится.

Остается только еще раз порадоваться, что появился Rust и разработчики вроде вас не будут отстреливать ноги ни себе, ни другим.
Тем не менее, вы хотите сравнивать Rust с Kotlin/C#. В этих языках полноценный GC, в Kotlin к тому же еще и null safety, насколько я помню, присутствует. Поэтому в плане прикладного программирования Rust мало кому интересен, в отличии от Kotlin-а.

Интересная ситуация. Как уже было сказано, в большинстве типичных прикладных задач автоматического управления памятью Rust'а должно быть достаточно (без циклических ссылок). А для больших и сложных приложений, уже ощутимо преимущество в производительности перед GC и контроле за выделением/освобождением памяти + возможность писать unsafe-код. Этого сильно нехватало в Java. Так что я бы не торопился с выводами.

Этого сильно нехватало в Java.
Этого настолько сильно не хватало в Java, что Java уже давно за глаза называют Cobol-ом XXI-го века — насколько много прикладного кода на Java уже написано, и огромное его количество пишется прямо сейчас. Не говоря уже про то, сколько еще будет написано.
И тем не менее, многие крупные системы, написанные на Java, пытаются решать связанные с ней проблемы регулярными перезапусками.
А разработчики этих систем утверждают, что системы такого объема и сложности было бы крайне тяжело и дорого разработать на языках без GC, если вообще возможно.

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

Уточнение: на языках без автоматического (ну или "полуавтоматического") управления памятью и с UB на каждом шагу.


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

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

Уточнение: на языках без автоматического (ну или «полуавтоматического») управления памятью и с UB на каждом шагу.
Я понимаю, что Rust-оманам очень приятно бросить шпильку в адрес C++, но в данном случае кроме демонстрации упоротости и ограниченности восприятия ничего не вышло. Во-первых, в C++ управление памятью такое же, как и в Rust-е. Как бы вам borrow-checker не грел душу, но управление памятью такое же.

Во-вторых, до C++ существовали языки без GC, но гораздо более безопасные. Достаточно вспомнить Pascal с его последователями. Objective-C и, в особенности, Ada, которая развивается до сих пор.

Только вот прикладной софт в массовом порядке пишут не на них, а на Java, C#, VisualBasic и т.д.
кто сегодня выбирает Rust для своих проектов.
Ну и кто это?
Во-первых, в C++ управление памятью такое же, как и в Rust-е. Как бы вам borrow-checker не грел душу, но управление памятью такое же.

Напомните, как C++ разруливает времена жизни ссылки и объекта, на который она указывает? Также, как и Rust?

Напомните, как C++ разруливает времена жизни ссылки и объекта, на который она указывает?
Вы не в ту сторону гребете. И в C++, и в Rust-е у вас есть автоматические объекты на стеке и есть объекты в динамической памяти (для простоты ограничимся только этим, без учета статических объектов и агрегатов). Объекты на стеке разрушаются автоматически при выходе из скоупа. Объекты в динамической памяти, при использовании Arc/Rc или shared_ptr разрушаются при обнулении количества ссылок на объект. Либо, при использовании Box и unique_ptr, при разрушении этих самых Box и unique_ptr. При этом сами экземпляры Box/Arc/Rc и unique_ptr/shared_ptr живут по тем же правилам, что и другие объекты на стеке или внутри агрегатов.

Так что в определении времени жизни объекта C++ и Rust следуют одним и тем же принципам. Только в Rust-е компилятор посредством борроу-чекера следит за тем, чтобы вы, например, не вернули ссылку на локальный объект, который будет разрушен при выходе из скоупа. Но сам локальный объект в Rust-е будет уничтожен при тех же условиях, что и в C++.

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

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

Так что в полной мере C++ не защищает от висячих ссылок, в отличии от языков с GC и safe Rust.
Вынужден еще раз подчеркнуть, что речь шла о механизмах управления памятью, а не о том, насколько просто получить невалидную ссылку.

Что такое умный указатель на стековый объект не понял. Как и смысла в оном (ни в C++, ни в Rust-е).
По вашему допуск/запрет висячих ссылок не имеет отношения к управлению памятью? То есть механизм обеспечения безопасности памяти при ее использовании не относится к управлению памятью?.. Нну-ну.
Прежде чем ответить, я бы хотел получить от вас пояснения по поводу «умного указателя на стековый объект».

Как я понимаю, unique_ptr/shared_ptr избавляют от ошибки использования после освобождения для объектов, размещенных в куче. За счет того, что они берут на себя управление уничтожением объекта. Но стековый объект будет уничтожен при выходе из области видимости. Что при этом произойдет с умным указателем, если он указывает на стековый объект? А в случае возврата такого умного указателя из функции? Можно ли с помощью умных указателей гарантировать отсутствие висячих ссылок в программе, если в качестве ссылок как на объекты, размещенные в куче, так и для объектов на стеке используются только они?

Как я понимаю, unique_ptr/shared_ptr избавляют от ошибки использования после освобождения для объектов, размещенных в куче

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


Задача умных указателей в C++ — это обеспечить своевременную деалокацию динамически созданного объекта. И делается это за счет создания пары из ресурса и хэндла для этого ресурса. При этом время жизни ресурса ставится в зависимость от времени жизни хэндла (хэндлов). Хэндл — это экземпляр умного указателя. В случае unique_ptr для ресурса хэндл единственный, в случае shared_ptr хэндлов может быть много.


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


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


void f() {
  MyData * data{};
  {
    unique_ptr<MyData> actual_data{new MyData{...}};
    data = actual_data.get();
  }
  data->some_method(); // Oops!
}

Или вы можете сделать вот такое:


unique_ptr<MyData> f() {
  MyData data{...};
  return {&data};
}

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


Теперь по поводу того, что "По вашему допуск/запрет висячих ссылок не имеет отношения к управлению памятью?"


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


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


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


Ничего в этом плане Rust не привнес. И ничего не забрал.


Почему типы указателей ссылок — это вторично? Да потому, что в C есть только указатели, а в C++ еще и ссылки. Но механизм управления памятью один и тот же. Собственно, Rust добавляет сюда еще больше своей специфики, но принципов работы с памятью наличие ссылок из safe Rust-а или голых указателей из unsafe Rust никак не меняет.


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

Так вот о том и речь, что в C++ нет механизма, позволяющего объекту своим временем жизни управлять ссылкой на него. Есть механизм, который позволяет ссылке управлять временем жизни некоторых объектов (умные указатели), но не наоборот. Поэтому на шкале от полностью ручного управления памятью до полностью автоматического, C++ расположен ближе к ручному, чем Rust. Так как программисту приходится самому следить за валидностью ссылок. Этот аспект не касается непосредственно выделения памяти или ее освобождения, он касается гарантии обращения к объекту только в промежутке межде этими двумя операциями. Понятно, что вам выгодно считать этот аспект вторичным и малозначимым, но с точки зрения прикладного программиста, это очень важное качество, которое принципиально отличает способ работы с памятью Rust от C++ и которое все-таки ставит Rust сильно ближе к языкам с GC.
> и которое все-таки ставит Rust сильно ближе к языкам с GC.

Это только в ваших сновидениях. Если в Rust-е разработчик разместил объект на стеке, а затем захотел продлить время жизни этого объекта (например, нужно сохранить ссылку на объект в каком-то мапе/реестре), то сделать он этого не сможет. По тем же самым причинам, что и разработчик на C++. Программисту придется менять код и размещать объект в хипе. Так же, как и в C++. Да еще и придется бодаться лайфтаймами.

Все это не нужно в языках с GC.

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

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

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

Приведите пример. Мне что-то не очень ясно, почему я не могу просто смувить структуру в этот самый мап.

Все это не нужно в языках с GC.

Там своих проблем хватает.

Но вам же важнее, чтобы Rust был лучше C++ (хотя вы C++ не знаете, вам должно быть фиолетово).

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

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

Если вы называете ручной поиск висячих ссылок и обвинение программиста в их наличие «пониманием работы инструмента» — то да, такого понимания не надо. Компилятор должен по максимуму отлавливать такие ошибки, а не «доверять программисту», который вчера перепил и написал чушь.
Мне что-то не очень ясно, почему я не могу просто смувить структуру в этот самый мап.
Извините, лимит политкорректности исчерпан. Было сказано, что сохранить нужно ссылку. Значит ни копию, ни перемещения использовать нельзя. Впрочем, уже было установлено, что вас всерьез нельзя воспринимать. Еще одно подтверждение — это ваша неспособность представить себе такой сценарий.
Важнее, что раст удобнее мейнстримовых языков с GC в большинстве задач.
Какие-нибудь доказательства этому будут? Или это из области вашего собственного опыта?
Если вы называете ручной поиск висячих ссылок и обвинение программиста в их наличие «пониманием работы инструмента» — то да, такого понимания не надо.
Если для вас понимание принципов тождественно ручному поиску висячих ссылок, то… То, блин, очень жаль, что профессия программиста за последние 20 лет скатилась до такого.
Нет. Важнее, что раст удобнее мейнстримовых языков с GC в большинстве задач.

но постойте… За то, что программист может спокойно сделать в языке с GC, компилятор раста даст по рукам. Это разве не относится к «удобству»?

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

блин, вас послушать, так на расте одни студенты-алкоголики и пишут…
но постойте… За то, что программист может спокойно сделать в языке с GC, компилятор раста даст по рукам. Это разве не относится к «удобству»?

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

но постойте… За то, что программист может спокойно сделать в языке с GC, компилятор раста даст по рукам. Это разве не относится к «удобству»?

Я уже говорил, в языке с GC есть отдельные костыли для работы с ресурсами — IDisposable/using/try-with-resource/… Так что во многих случаях как раз в языке с GC мороки больше, потому что там, где в расте будет вызван автоматический деструктор, в GC языке нужно руками все обернуть.

блин, вас послушать, так на расте одни студенты-алкоголики и пишут…

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

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

Я оставляю это за кадром, потому как остановка мира и предсказуемость никак не связаны с удобством разработки.
Я уже говорил, в языке с GC есть отдельные костыли для работы с ресурсами — IDisposable/using/try-with-resource/…
Ну посмотрите на Go с defer-ом. Или на D со scope(exit). Мир безопасных языков с GC не ограничивается C# и Java. Тем более, что вы безопасность в том же C# умудряетесь творчески сломать через колено.
Тем более, что вы безопасность в том же C# умудряетесь творчески сломать через колено.

Кроме меня, на проектах обычно работает по 30-40 человек. Удобно считать всех остальных людей идиотами, конечно, но может так оказаться, это не совсем так.
Если в Rust-е разработчик разместил объект на стеке, а затем захотел продлить время жизни этого объекта (например, нужно сохранить ссылку на объект в каком-то мапе/реестре), то сделать он этого не сможет. По тем же самым причинам, что и разработчик на C++.

Да, но только разработчик на C++ все же сможет сохранить ссылку, тогда как время жизни объекта продлено не будет. Язык с GC в этом случае позволит сохранить ссылку, увеличив время жизни объекта путем размещения его в куче, вместо стека. Rust же просто не скомпилирует такой код, пока программист вручную не обеспечит соответствие времен жизни. То есть в Rust, также как и в языках с GC, не возникнет ситуации с висячей ссылкой.


Конечно, на C++ возможно программировать так, чтобы небыло ошибок с памятью. Проблема в том, что это не обязательно.

То есть в Rust, также как и в языках с GC, не возникнет ситуации с висячей ссылкой.
Во-первых, к механизму управления памятью это не имеет отношения.

Во-вторых, unsafe — это часть языка. При это такая часть, которую даже в этом обсуждении уже амнистировали для тех же самых сложных структур данных.
Конечно, на C++ возможно программировать так, чтобы небыло ошибок с памятью. Проблема в том, что это не обязательно.
Етить-колотить. Да вы не понимаете две простых вещи:

1. Rust использует тот же самый механизм управления памятью, что и C, и C++. Но за счет ограничений, нагалаемых компилятором, механизм этот становится безопаснее. Т.е. механизм тот же, но использовать его безопаснее. Хотя и не бесплатно, есть дополнительный геморрой как при изучении языка, так и при попытках выразить в Rust-е некоторые структуры данных.

2. Ручное управление памятью, пусть даже и безопасное, просто нафиг не упало в прикладном программировании. Вот просто не упало. Поскольку оно не бесплатно: либо вы живете с повисшими ссылками (как в C++), либо страдаете с выражением двусвязных списков.

Это вы не понимаете, что обеспечение соответствия времен жизни объектов и ссылок на них — важная часть механизма управления памятью. В Rust, также как и в языках с GC, эта часть присутствует, хотя и работает отлично от них. А в C++ ее просто нет. Поэтому для тех прикладных задач, где все же важен контроль, либо критичны издержки, налагаемые GC, Rust вполне применим. Тогда как C++ расположен уровнем ниже.

Это вы не понимаете
Сказал персонаж, который не хочет разбираться в принципах работы инструмента. И сравнивает Rust с C++ не имея представления о C++.
Поэтому для тех прикладных задач, где все же важен контроль, либо критичны издержки, налагаемые GC
Может вы хотя бы приблизительный список таких прикладных задач озвучите?
Это только в ваших сновидениях. Если в Rust-е разработчик разместил объект на стеке, а затем захотел продлить время жизни этого объекта (например, нужно сохранить ссылку на объект в каком-то мапе/реестре), то сделать он этого не сможет. По тем же самым причинам, что и разработчик на C++. Программисту придется менять код и размещать объект в хипе. Так же, как и в C++. Да еще и придется бодаться лайфтаймами.
Все это не нужно в языках с GC.

Все-таки, не совсем. В go, если я ничего не путая, будет проведен escape-анализ, и переменная, выглядящая, как стек, неявно для программиста переедет в кучу. Что может иметь последствия. И накладывает ограничения на семантику языка, бо не все в такой ситуации реализовать легко и приятно.
В jvm-языках, все или в куче, или в стеке. И если в стеке, то в кучу может переехать только за счет автобоксинга вроде, который надо написать.
  • смарт-указатели на стековый объект (без особого синтаксиса с Deleter) — конечно приведут к вылету, но такое смешение кучи и стека — признак пару-дневного новичка. для стековых объектов нужно работать со ссылками, а не указателями.
  • на остальные вопросы ответ положительный — их можно возвращать и утечек и повисших указателей не будет.
  • с другой стороны — в С++ широкий выбор пистолетов для стрельбы в ногу и от них не уйти — только заставить не использовать, что сложнее, даже для себя
> и повисших указателей не будет

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

жаль, что вы ссылаетесь на мнение не совсем компетентного автора:
В целом, на С++17 уже можно писать вполне себе чистый код, правда он не будет настолько же эффективным, как его менее чистый аналог.
НЛО прилетело и опубликовало эту надпись здесь
Я говорю лишь о тех UB, которые, как бы это сказать, на самом деле происходят

Извините, но вот из этой фразы: «компилятор рано или поздно утыкался в UB» это никак не следует, поскольку компилятор практически постоянно утыкается в UB.

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

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

Не знаю, о ком вы говорите и кто минусует. Лично я еще ни одного минуса "плюсовикам" не выдал. В плюсах разбираюсь слабо, поэтому обычно дискуссии Rust vs. С++ читаю c интересом. Так что обобщать и всех мазать одной краской, думаю, не стоит.

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
устал от HR, приглашают на работу и собеседования каждую неделю. Приходится вслушиваться в произношение людей из Шотландии и Новой Зеландии
приходится скрывать доходы от друзей, потому что зарплату в $$$ реально девать некуда.


Уговорили, rust то я давно начал изучать, всё никак серьёзно за него не возьмусь…
Пора обновить старый стишок к новым реалиям
The code I see
Make sense to me
But GCCrustc…
It disagrees
По началу, пока с владением не разобрались. Можно в неком подобии функционального стиля писать. Например вот пример функции для сложения и умножения в обоих стилях.
Код, лайфтаймы тут только для процедурного варианта. Если оставить только функциональный то их можно убрать
use std::ops::{Add, Mul};
use std::borrow::Borrow;

enum Operation {
    Add,
    Mul,
}

struct Calculator<T> where {
    pub  op: Operation,
    pub    lhs: T,
    pub  rhs: T,
    pub result: Option<T>,
}

impl<'a, 'b: 'a, T: 'b + Add<Output=T> + Mul<Output=T> + Borrow<T>> Calculator<T> where &'a T: Add<Output=T> + Mul<Output=T> {
    pub fn calculate_procedurally(&'b mut self) {
        let res: T = match self.op {
            Operation::Add => &self.lhs + &self.rhs,
            Operation::Mul => &self.lhs * &self.rhs,
        };
        self.result = Some(res);
    }
}

impl<T: Add<Output=T> + Mul<Output=T> + Clone> Calculator<T> {
    pub fn calculate_functionally(mut self) -> Self {
        self.result = Some(
            match self.op {
                Operation::Add => self.lhs.clone() + self.rhs.clone(),
                Operation::Mul => self.lhs.clone() * self.rhs.clone(),
            }
        );
        self
    }
}

#[cfg(test)]
mod example {
    #[test]
    fn test_add() {
        let mut calc = self::super::Calculator::<isize> {
            op: self::super::Operation::Add,
            lhs: 2,
            rhs: 3,
            result: None,
        };
        calc = calc.calculate_functionally();
        assert_eq!(5, calc.result.unwrap());
    }

    #[test]
    fn test_mul() {
        let mut calc = self::super::Calculator::<isize> {
            op: self::super::Operation::Mul,
            lhs: 2,
            rhs: 3,
            result: None,
        };
        calc = calc.calculate_functionally();
        assert_eq!(6, calc.result.unwrap());
    }

    #[test]
    fn test_add_proc() {
        let mut calc = self::super::Calculator::<isize> {
            op: self::super::Operation::Add,
            lhs: 2,
            rhs: 3,
            result: None,
        };
        calc.calculate_procedurally();
        assert_eq!(5, calc.result.unwrap());
    }

    #[test]
    fn test_mul_proc() {
        let mut calc = self::super::Calculator::<isize> {
            op: self::super::Operation::Mul,
            lhs: 2,
            rhs: 3,
            result: None,
        };
        calc.calculate_procedurally();
        assert_eq!(6, calc.result.unwrap());
    }
}


В статье есть упоминание, что деревья, списки и др. конструкции тяжело кладутся на концепцию borrow-checking Вопрос: в итоге как эта проблема решается?


  • списки и деревья просто не используются? не могу себе такого представить
  • их сложнее реализовать, но всё же удобно использовать (как и в других языках)?
  • их сложно не только реализовать, но и использовать? боль?

Просто, мне кажется, что вне зависимости от того, насколько они подходят под borrow-checking или нет, всё же без списков, деревьев и прочих базовых структур не может обойтись ни 1 императивный язык и ни 1 мало мальски сложная алгоритмически программа. За ФП не скажу. Как этот вопрос решается?

Без unsafe их писать сложно. Использовать их чуть более удобно, чем в других языках без GC. До языков с GC удобство использования конечно не дотягивает.


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

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

Ну не знаю. Вот в JS я практически 90+% времени работаю с деревьями. Я имею ввиду не бинарные древа поиска или что-то подобное. Но тасую туда-сюда данные пачками, вездесущие рекурсии, множество перекрёстных ссылок. В общем самые разнообразные безобразия с графами данных. В случае Rust я бы свихнулся?


Вот скажем взять модный нынче virtual-dom. Это большое древо элементов. Мутабельное. Типичная задача по работе с ним — бегать по нему и обновлять что-нибудь в его звеньях. Такое в Rust будет болезненным?


в системном программировании же ещё реже.

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

js-code
const tree = {
    a: {
        b: {
            c: 1
        }
    }
};

const b = tree.a.b;

const inc = tree =>
{
    for(const [k, v] of Object.entries(tree))
        if(typeof v === 'number')
            tree[k] += 1;
        else
            inc(v);
};

inc(tree);
b.c += 1;
b.c; // 3

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

До появления NLL вас бы и правда компилятор часто "бил по голове" на каждом шагу. Но с появлением NLL все стало куда проще.

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

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


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


Если же вам такое нужно постоянно, то придется использовать контейнерные типы для внутренней изменяемости, такие как RefCell:


use std::rc::Rc;
use std::cell::RefCell;

struct MyStruct {
    a: Rc<RefCell<i32>>,
}

fn main() {
    let a = Rc::new(RefCell::new(0));
    let s = MyStruct { a };

    let b = s.a.clone();

    *s.a.borrow_mut() += 1;
    *b.borrow_mut() += 1;

    println!("{}", s.a.borrow());
}

Запустить


Как вы можете видеть — это больно, но терпеть можно :)

Не забываем галочку markdown, без неё все плохо
Допустим, у нас есть структура данных, за лайфтайм которой отвечает одна сущность (A), а за чтение/запись — другие (B, C, D). Предположим, я дал компилятору понять что A всегда переживет B,C,D и вопрос только в разделении мутирующих ссылок на B,C,D. Вы предлагаете Rc/Arc/RefCell — наипростейшее решение, но оно совершенно не эквивалентно разделению сырых ссылок в многопоточном приложении — Rc может выстрелить в ногу, а Arc/RefCell — существенно замедлить алгоритм. Получается выбор между повсеместным unsafe и стоимостью в рантайме и в подавляющем большинстве случаев выиграет первое. Я правильно понимаю что целый класс задач решается через повсеместный unsafe?

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

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

Управляемый объект может быть организован так, что операции над ним потокобезопасны (например, обертка над библиотечным). Так зачем мне дополнительная прослойка?
Arc не отвечает за синхронизацию доступа к объекту между потоками, а только за его удаление.

Это детали. Может не Arc, а Mutex<Arc> или ещё что-то такое.


Управляемый объект может быть организован так, что операции над ним потокобезопасны (например, обертка над библиотечным).

Да, может. И в случае именно Rust от такого объекта ожидается, что эта его потокобезопасность будет выражена в API этого объекта. Потому что Rust содержит необходимые средства для этого. Если API объекта не сообщает компилятору, что его можно свободно мутировать, то вам придётся сделать над объектом обёртку с unsafe кодом. И дальше вы сможете работать с объектом через эту обёртку без обращения к unsafe.

Вот тут пример того как работают с dom в Rust, wasm фреймворк вдохновленный React и Elm — Yew
Вот скажем взять модный нынче virtual-dom. Это большое древо элементов. Мутабельное. Типичная задача по работе с ним — бегать по нему и обновлять что-нибудь в его звеньях. Такое в Rust будет болезненным?

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


А иметь больше мутабельное дерево в расте действительно будет неудобно, скорее всего.

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


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


  1. Использовать Rc, RefCell и прочие Weak; или
  2. Использовать сырые указатели и управлять освобождением памяти вручную; или
  3. Хранить узлы во внешнем контейнере и ссылаться на них по индексу; или
  4. Использовать готовые библиотеки, которые скрывают всю кухню за своим API.

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


Собственно, из-за чего вознрикает проблема с графовыми структурами?


Обычно, в них у вас есть некий объект и вы хотите хранить ссылки на него в нескольких местах одновременно. Но тут возникает сразу две проблемы:


  1. Объект должен быть доступен из разных мест по ссылке, и удален только тогда, когда на него нет ссылок. Если есть по крайней мере одна ссылка, то объект должен жить.


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



Первая проблема решается подсчетом ссылок (reference counting) и в других популярных прикладных ЯП она обычно скрыта от пользователя за реализацией. То есть любая "ссылка" на объект в таких ЯП всегда есть умный указатель со счетчиком ссылок. В Rust же такого типа ссылку нужно создавать вручную, если она вам нужна. Для этого используются типы Rc и Arc. Кроме того, нужно как-то решать вопрос образования циклических ссылок, для чего используется Weak.


Вторая проблема решается введением совместно используемых изменяемых контейнеров — это типы Cell, RefCell, Mutex, RwLock.


Соответственно, ссылки с типами вроде Rc<RefCell<Node>> всех и пугают.


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

Спасибо за такой развёрнутый ответ ;)

Их сложнее реализовывать — нужно руками оборачивать в умные контейнеры Rc/Arc/RefCell/..., о чем я в статье упомянул, пользоваться соответственно слегка труднее, чем значением напрямую — нужно работать через АПИ умного указателя.

Наличие LinkedList в стандартной библиотеке говорит о том, что реализовать, конечно же, можно.

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

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

А python не зайдет? Со всеми клевыми numpy и pandas?

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

Отлично зайдёт для многих задач, но в какой-то момент можно внезапно споткнуться о GIL и будет очень обидно, поскольку по остальным параметрам всё очень классно, а решить задачу так же красиво, как на Python, на чём-то другом будет достаточно нетривиально.
Например, будет большая модель данных, часть обработки/доставки этих данных придётся сделать на уровне Python, поскольку низкоуровневые библиотеки, умеющие работать параллельно в несколько потоков, именно такую обработку сделать не могут, и придётся искать решение вопросов в духе "где взять больше памяти" и "как эффективно синхронизировать между процессами большие объёмы данных".

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

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

А в чем тогда его киллер фича кроме жесткого компилятора в плане управления памятью, что для скрипта скорее минус чем плюс?

Даже для скрипта желательно чтобы он делал то, что ожидается. А плюс в том, что если я пишу скрипт на каком-нибудь python/powershell, я его запускаю тонну раз, и проверяю. А в данном случае если код скомпилировался, значит всё правильно. Можно для очистки совести прогнать разок на тестовых данных, но постепенно и это подспудное желание, вызванное опытом на других языках, проходит.


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

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


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




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


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


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

Если мне нужна безопасность, то почему не Haskell? В несколько раз лаконичнее, выразительнее и безопасный.

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

Года пол назад видел обсуждение на ycombinator'е или где-то еще, как раз от хаскеллистов, что раст решает один класс проблем, а хаскель — другой, и в некотором степени они ортогональны. Могу поискать, если сильно интересно. Но вообще выходило, что в хаскелле отстрелить себе ногу вполне можно. Речь как раз про IO была и некоторые другие вещи, насколько я помню.
НЛО прилетело и опубликовало эту надпись здесь
Знание польского делает unsafeDupablePerformIO несколько двусмысленным))) Не смог терпеть)
НЛО прилетело и опубликовало эту надпись здесь
Я польский не знаю, но чётко увидел там слово «дупа»…
А в данном случае если код скомпилировался, значит всё правильно.

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

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

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

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

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

Это да. Тогда раст не подходит, и берем что-то другое.

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

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

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

Давайте. 80% багов, которая я когда-либо чинил, были связаны с нуллами, неявными приведениями, и комбинацией этих двух вещей. Раст обе проблемы решает радикально. Из остальных процентов 15 это бросили эксепшн, который не ожидался, и который никто не собирался ловить. Ну и 5% на все остальные случаи.

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

Ту же историю я помню лет 20 назад, только надо поменять раст на джаву, а под популярным языком иметь в виду COBOL
НЛО прилетело и опубликовало эту надпись здесь

Но есть же [в IO].

НЛО прилетело и опубликовало эту надпись здесь
Какой выход/вывод? Забить на IO, потому что он «unsafe» в хаскелевом смысле?
НЛО прилетело и опубликовало эту надпись здесь
Что это? Упрощенная форма CSP? Только флажком коммуницируем между параллельными потоками? Расшаренный стейт вообще нельзя? А что если, у меня например большой стейт в памяти и я хочу к нему периодически обращаться из нескольких потоков, но при этом держать его копию для каждого потока это много. Как тогда быть?
НЛО прилетело и опубликовало эту надпись здесь
80% багов, которая я когда-либо чинил, были связаны с нуллами, неявными приведениями, и комбинацией этих двух вещей.
Да вам можно просто позавидовать.

Еще хорошей пример — байка про то, как я ловил баг в Linq2Sql. У нас были тесты, которые проверяли весь функционал. И баг, который в тестах не воспроизводился. У клиента был чек на 1000 рублей, была скидка 0 рублей, в итоге ему выставлялся счёт в 0 рублей. Начал разгребать, оказалось, искал много дней баг, наконец нашел. Дело в том, что у нас был запрос вида


var dicsount = GetClientDiscount().Where(x=>SomeCondition(x.SomeField)).Sum();
var totalPrice = price - discount

И если у чувака не было скидок, то LINQ возвращает 0


Только вот когда идет запрос в БД, то ёSELECT SUM(SomeField) from T where SomeConditionё возвращает для пустого множетсва ответов null.


а дальше всё просто


var discount = null
var totalPrice = 1000 - null // null

Дальше null при конверсии при печати превращался в 0 и мы имели тот результат, что имели.


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


В итоге я написал рерайтер SumExpressionVisitor, который применял преобразование к дереву, и везде заменял query.Sum(x=>x.Foo) на query.NullIfEmpty()?.Sum(x=>x.Foo). Там же кстати пример с тем, как я использовал для тяжелых запросов Like так, чтобы в тестах отрабатывало.


Большинство проблем и решений в моем шарповом опыте выглядит как-то так, да.


С логикой обычно проблем нет, потому что на неё пишутся тесты. А вот на неявную конверсию null -> 0 и различие поведения in-memory DB и реальной — увы.

А покажите пример этого же кода на Rust-е, плиз.

ну взаимодействие с БД нам в данном контексте не сильно интересно, а вот в этом месте:


let discount: Option<i64> = None;
let totalPrice = 1000 - discount; 
//  no implementation for `{integer} - std::option::Option<i64>`

Ну хорошо, я напишу вот так:


let totalPrice = match discount {
    Some(v) => 1000 - v
    None => None
}

Стало лучше? :-) Ошибка-то, на самом деле, в логике запроса и в логике печати...


Каким вообще образом у вас получилось получить для linq to objects-запроса тип int, а для linq to sql — тип int? ?

Стало лучше? :-) Ошибка-то, на самом деле, в логике запроса и в логике печати...

Стало, потому что
а) есть явная обработка null-а
б) вы бы не смогли это вывести на печать:


let discount: Option<i64> = None;
let totalPrice = discount.map(|x| 1000 - x);
println!("Общая стоимость: {}", totalPrice);
// `std::option::Option<i64>` cannot be formatted with the default formatter

В общем, добавляется как минимум 2 места, где скорее всего была бы вставлена корректная обработка этой ситуации.

ну взаимодействие с БД нам в данном контексте не сильно интересно
Ошибаетесь, там все интересно, в том числе и проброс ошибок. Кроме того, даже показанный вами пример — это и не пример вовсе. Поскольку получив такую ошибку от компилятора разработчик написал бы что-то вроде
let discont = GetDiscount(...);
let totalPrice = 1000 - discount.value_or(0);
Компилятор был бы удовлетворен, а вот корректен ли был бы такой код и устно ли было бы применение значения 0 — это отдельный вопрос.

У меня поверхностные впечатления от C#, но, ИМХО, вы бы там могли бы иметь тоже самое. Какой-нибудь NullableValue и писали бы что-то вроде:
NullableValue<Int> discount = GetClientDiscount()...;
var totalPrice = 1000 - discount.ValueOr(0)

И, по сути, ваша претензия сводится к тому, что в C# вам приходится пользоваться API, в котором нет Option или чего-то подобного. Хотя язык этому и не препятствует.
Компилятор был бы удовлетворен, а вот корректен ли был бы такой код и устно ли было бы применение значения 0 — это отдельный вопрос.

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


У меня поверхностные впечатления от C#, но, ИМХО, вы бы там могли бы иметь тоже самое. Какой-нибудь NullableValue и писали бы что-то вроде

Nullable<decimal> там и был :) Только вот из-за всяких неявных кастов и того, что они не являются объектами первого класса во многих случаях (а также из-за различий reference/value types) получается фигня.


И, по сути, ваша претензия сводится к тому, что в C# вам приходится пользоваться API, в котором нет Option или чего-то подобного. Хотя язык этому и не препятствует.

Это просто еще камешек в огород, чего мне в шарпе не хватает. А отвечал я на то, какие баги я в шарпе чинил, которых в расте бы не возникло. Вы задали вопрос "а на что там 80% времени уходит", я показал конкретный пример из реального проекта.


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

Вы задали вопрос «а на что там 80% времени уходит»
Я не задавал такого вопроса, вы путаете. Я сказал о том, что если у вас 80% ошибок — это null и неявные преобразования, то вам можно только позавидовать. И пока что остаюсь при мнении, что основная работа у вас из категории «не бей лежачего».
Давайте. 80% багов, которая я когда-либо чинил, были связаны с нуллами, неявными приведениями, и комбинацией этих двух вещей. Раст обе проблемы решает радикально. Из остальных процентов 15 это бросили эксепшн, который не ожидался, и который никто не собирался ловить. Ну и 5% на все остальные случаи.

у меня совершенно другая статистика. 80% проблем это логические ошибки вроде забыли в ответ прокинуть какое-то поле. или не учли какой-то вариант входящих данных. или другой сервис отдал какую-то херню вместо json. или написали кривой запрос в бд. может быть у вас сервисы не очень сложные которые не ходят во внешние сервисы или БД?

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


написали кривой запрос в бд

на ORM трудно написать кривой запрос. Можно написать неоптимальный, но оптимизация производительности != починка бага.

большинство проблем возникает из-за логических ошибок


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

в теории можно писать идеальный код, который сразу компилируется и сразу работает как надо на любом языке. но почему-то никто не пишет.
Так для справки — «зеленые потоки» т. е. проще говоря пул потоков ОС с шедулером уже несколько лет не «киллер фича Go» потому что другие языки тоже развивались знаете ли. Да в далеком 2009 году это была «киллер фича». Мы через несколько дней в 2019 будем. С наступающим кстати. Макросы, Борров чекер и лайфтайм, единый бинарник, скорость сравнимая с С\С++, отстуствие рантайма что в результате позволяет его таки адекватно в wasm использовать. У Rust wasm файл меньше мегабайта получается в отличии от Go у которого хеллоу ворд весит несколько мегабайт потому что шедулер, gc вот это все надо тащить с собой. + Та же мощь что и у C\C++ т. е. можно хоть свою операционную систему написать на Rust. Вообще мне нравиться С просто он слишком опасный по сравнению с Rust поэтому мой выбор Rust. Мне мои нервы дороже.
и Вас с наступающим! надеюсь rust вас не разочарует

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

Ну есть параллельные итераторы из rayon, возможно вам подойдут:


(1..100500).into_par_iter().for_each(...)

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

Тогда на выбор акторы или футуры с async/await (пока только в nightly)


let foo = await!(something);
let bar = await!(else);
...

или без них на стейбле


something.and_then(|foo| {
   else.and_then(|bar| {
      ...
   }
})

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

Что именно? Вы пишете «ручками» толкьо необходимый пайплайн «какие данные откуда взять и куда положить», дальше отдаете футуру в реактор, она там как-то крутится. Полностью аналогично JS/C#/..., кроме того, что там реактор автоматически футуру подцепит, а в расте надо явно отдать на выполнение.
Вот последнее и выглядит разительным отличием «The whole system is built around this: for example, cancellation is dropping the future for precisely this reason. In contrast, in other languages, calling an async fn spins up a future that starts executing immediately.»
Не очень js точно.

И не хватает механизма «запустить эту функцию асинхронно» без того, чтобы делать async обертки.
Я понимаю, что от поддержки green threads отказались, как от слишком «opinionated», но предложенный механизм довольно строг и скорее похож на синхронное выполнение кода, чем на асинхронное.

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


Я не писал ни разу руками футуры, а запускал просто реактор в виде:


fn main() {
    let listening_address = "127.0.0.1:8080";
    let server = Server::bind(listening_address)
        .serve(|| {
            service_fn(|x| handle_request(x))
        })
        .map_err(|e| error!("server error: {}", e));

    info!("Listening on http://{}", listening_address);
    rt::run(server);
}

async fn handle_request(
    req: Request<Body>
) -> Result<Response<Body>, hyper::Error> {
   ...
}

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

Ну, с точки зрения еще и го программиста, то плохо :)
Посмотрел еще раз rfc 2394. Соглашусь, что как первая имплементация — неплохо. Но пока не хватает гошной гибкости наптсания сложного конкурентного кода без дополнительных обвязок и ограничений. И все же решение раста для меня, скорее, weak async, если можно так сказать, из-за названной выше особенности времени выполнения.

Ну может быть, не готов сказать.

Что в шарпе, что в расте, что в JS — пишешь себе комбинаторы, а они там как-то выполняются :)

Хех, последнее неоспоримо :)

один бинарник без зависимостей, и вот это всё.

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

Ну это прикольно, да.

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

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

Го настолько упорот что не использует libc на OS X где кроме libc стабильных api нет :) полгода назад пробегала баженька что все гошные бинари разом сломались на свежем релизе макоси потому что там что-то в ядре поменяли.

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

Что-то больно у вас легко всё.


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


  2. Использовали ли вы RefCell? А Mutex? А Mutex<RefCell>? А сочетание всего этого с Option? Код, который в C# или там Python выглядел бы как a.b().c начинает выглядеть примерно как a.lock().unwrap().b().as_ref().unwrap().c



На Rust действительно приятно писать код. Он действительно является привильно сделанным Си. Это всё правда. Но если вы можете конкретную штуку написать на чём-то другом — ну, например, F# — лучше писать на другом. Возможно, вы получите меньше удовольствия, но код точно будет существенно короче.

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

lock(_lock)
{
   return a.b().c; 
}

а в расте будет


a.lock().unwrap().b()?.c

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


И, кстати, в вашем C# коде ошибка, потому что если b() вернет null, то вы получите NullReferenceException в рантайме.

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


если b() вернет null, то вы получите NullReferenceException в рантайме

Всё правильно, там и должен быть NullReferenceException. В чём ошибка?

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

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

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

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

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


Если у вас есть специальный цикл в обратном порядке (n downto 1), то это более выразительный способ выразить идею "получить номера от n до 1", а вот (1 to n).reverse() будет менее наглядным, но более стандартным.


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

Синтаксис наподобие `n:-1:1` а-ля MatLab считается? Или здесь выразительность как раз страдает, потому что общую конструкцию (для цикла с произвольным шагом) натягивают на частный случай?

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


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

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


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

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


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

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

Операторы — это просто более удобная запись функций. В том же расте вы просто реализуете трейт Add/Mul/Div/..., а дальше можете пользоваться a.add(b) наравне с a + b. Операторы просто удобнее читать. Прибитые гвоздями операторы (Как в C# например) это действительно не очень.


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

Да, расширяемая контекстно-независимая (по возможности) грамматика это прекрасно. Но сахара тоже хочется.

НЛО прилетело и опубликовало эту надпись здесь

Их можно перегружать, но нельзя написать T Sum<T>(T[] items) where T : Add<T>


Или я не очень понял о чём речь — потому что на мой взгляд, разницы между add(a,b) и operator+(a,b) абсолютно никакой, если в результате и то и то будет использоваться как a+b

В этом и разница. По операторам невозможны генерики/полиморфизмы. Нельзя сделать Vec<dyn Add<i32>> как в расте, и пихать разные объекты, которые умеют складываться с i32, и написать какую-то общую фунцкцию обработки, и вот это всё.


Из-за этого в LINQ миллиард методов Sum для всех числовых типов, а можно было бы ограничиться одним генериком.

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

А во-вторых, самое важное: по моим ощущениям Rust — первый язык, который решил попробовать «с другой стороны». Все предыдущие языки (может быть, за вычетом лиспо-ml'ной школы) подходили с вопросом «как мне сделать X?». В ответ на разные X разные языки предлагали разные ответы.

Rust же подходит с вопросом «как мне НЕ сделать Y?», где в Y — все ужасы неопределённого поведения C, плюс чуть-чуть других гадостей.

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

Эта революция для тех, кто никогда не сталкивался с линтерами. Те, кто сталкивался, могут вспомнить: linter надо ублажать. Ублажаешь — в коде благодать.

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

Спасибо, мотивировали попробовать

IMO: Cложность написания структур, решится реализацией структур в отдельных библиотеках, ака java.util.Collections, что решит проблему быстрого написания онных с нуля.
Язык перживает основной этап становления на ноги, многи типовые задачи/решения переписываются на него для дальнейшего использования (ORM фраемворки, web фраемворки, Webassembly и тд.)
Ну и конечно же хотелось бы отметить, что в новом году, Rust сообщество также сосредоточится над реализацией async/await концепции.
На мой взгляд, у Rust есть все шансы стать языком следующего поколения.
Не уверен, верно ли считать универсальным языком тот, на котором некоторые алгоритмы не возможно реализовать в принципе, и которому не все структуры данных подходят.

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

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

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

Вообще-то, ни один язык хоть немного ориентированный на практическое применение не требует доказательств всего и вся. В Rust это — unsafe, в Agda — trustMe и т.д.


Хотя доказательство корректности вполне себе полезное дело — позволяет превентивно избежать некоторых плясок с бубнами отладчиком в 3 часа ночи пока сервис лежит. Попытка доказать корректность некорректной системы позволяет найти нетривиальные дыры, которые могут проскочить через батареи тестов, fuzzer'ы, санитайзеры, code review и т.п.

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

То есть, если у нас быстрое железо, то на корректность можно забить? Ну придет пользователю от 0 до 3 емейлов вместо ровно одного, ну так и ладно.


Корректность программы с GC и ручными управлением памяти не связано от слова никак.


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

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

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


Иррациональная же (собственно мифологическая) часть — это то, что если вы в Rust-программе использовали unsafe, то это, якобы, неправильно. Возможно, играет роль схожесть ключевого слова с unsafe C#/.NET, которое и в самом деле является не столько частью языка/платормы, сколько расширением, и по-умолчанию недоступно.


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

Я от себя добавлю такое простое наблюдение. `unsafe` в библиотеке — это норма жизни, именно потому, что в некоторых случаях иным образом не удастся реализовать удобную функциональность. `unsafe` в пользовательском коде — либо признак, что используемых библиотек недостаточно, либо признак, что сам этот код реализует какую-то нестандартную возможность. Не претендую на истину, но пока всё выглядит именно так.
Да нет никакой семантической разницы между unsafe в C# и unsafe в Rust. В обоих случаях это слово позволяет случайно нарушить внутренние инварианты языка, которые обычно гарантируются компилятором и рантаймом.

И используется оно одинаково: внутри изолированного набора подпрограмм, для которых внеязыковыми средствами доказывается правильное поведение во всех случаях.
В C# unsafe дает доступ к возможностям/синтаксическим конструкциям, недоступным в языке без unsafe.

Идеология разная. В случае C# есть контекст, где использовать unsafe нельзя, в случае Rust такого нет. То есть в C# разделение на safe и unsafe используется для безопасности, а в Rust — исключительно для удобства программиста.

То есть в C# разделение на safe и unsafe используется для безопасности, а в Rust — исключительно для удобства программиста.
Т.е. вы сейчас всерьез утверждаете, что в Rust-е разделение на safe и unsafe — это не для безопасности? O_o
Для безопасности, но только как следствие корректности: в Rust нет различия между корректным safe-кодом и корректным unsafe-кодом, просто корректный safe-код писать легче. А в C# есть ещё всякие security level и тому подобные рантайм-механизмы, из-за которых сборка, содержащая unsafe код, принципиально отлична от не содержащей такового, даже если unsafe код корректен и даже если он вообще ничего не делает.
А в C# есть ещё всякие security level и тому подобные рантайм-механизмы
Не уверен, что это свойство именно языка C#, а не .NET-а в целом (емнип, там вообще без разницы, из какого исходного языка сборка была получена).

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

Вы зря спорите. У unsafe в этих языках есть общие черты, и есть различия. Что представляет собой тот и другой, думаю, все и так знают. А рассуждать, что слон больше похож на змею или на колонну это дело такое :)


Ну конечно речь о платформе. Но тут одно от другого почти неотделимо.

Вполне отделимо. C# развивается с каждым годом, выходят новые версии.


Рантайм же не менялся с введения генериков 13 лет назад.

Думаю нет, для использования ассембля с небезопасным кодом пользователю нужны определенные права. Когда .NET только вышел, все ржали над тем, что с сетевой шары запустить менеджед код нельзя, а нативное приложение (которое вообще все что хочешь может) — можно. Безопасность имени МС — под фонарем у нас все в порядке, а темные углы — это не к нам.))))
В песочнице .net запрещен не только unsafe, но и огромная часть стандартной библиотеки…

Публикации

Изменить настройки темы

Истории