LINUX.ORG.RU

C++ vs Rust: правда ли, что Rust тупо сложнее крестов в базовых сценариях применения?

 


0

6

Если C++ разраба заставить писать код на определённом подмножестве C++ (в первом приближении: не выделять память руками, не юзать указатели, не кастовать типы), то в принципе течь и падать там будет негде. На деле чуть тоньше и проще: указатели можно, но если тебе его передали в конструктор. Есть циклические ссылки, но тоже можно чё-то придумать. В общем, чёткого набора рецептов нет, опытный разраб в конкретном проекте выработает свои достаточно рабочие, плюс статический анализ и прочие там sanity-check тулзы и valgrind-ы скажут где насрано автоматически.

А есть просто Rust, где достаточно запретить писать unsafe и всё будет гарантированно блестяще и даже думать не надо.

Но говорят, Rust сложнее. Думать там надо уже просто чтобы базово взлететь, тогда как для базового взлёта на C++ достаточно быть тупорылым сишником, которому запретили выделять память. Гонят?

! ! ! ААААА ПРОСЬБА ПЕРЕНЕСТИ В TALKS ОШИБСЯ ФОРУМОМ ! ! !

Дополнение к уже написанному в треде.

  1. Тяжёлое наследие плюсов: его нет, если его не юзать. Я же не пишу в Rust ассемблерные вставки везде. Возможность их написать же не говорит о том, что у Rust тяжёлое наследие всей x86/ARM аппаратной платформы. Нормальный C++ код не содержит никаких макросов, например и передач указателей в пределе. Передай ты std::span, std::string_view и т.п. вместо (char* ptr, uint32_t size).

  2. Никогда не понимал тезис, что синтаксис обычного Си - сложный. Он может быть сложный во всех ВОЗМОЖНОСТЯХ, но в базовых сценариях он кажется примитивным: int function_name(int a, int b) { return a * b;} - это же предельно тупейшая идея синтаксиса, которую придумает любой школьник при наличии задачи изобретения ЯП. Даже конфиги хочется писать в таком стиле - см nginx. Так вот, если сознательно не усложнять себе жизнь, то C++ так же прост.

  3. Посмотрим на такие конструкции Rust, выдернутые из контекста:

languages.get_statistics(&input, &cli.ignored_directories(), &config);

Имеются какие-то &. Не знаю что это, но почему не написано input вместо &input? То есть, юзера заставляют думать про разные виды передачи аргумнетов чтоли? Ссылочно/указательно? Чем это отличается от необходимости в крестах думать про rvalue, lvalue, reference, pointer? То есть, от этого момента язык тоже не ушёл: нельзя как в JS/Python херануть объект в аргумент и зашибись - надо думать как херануть.

(0..10).map(|_| "#").collect::<String>()

Питонячно. Какой-то генератор с вызовом какой-то лямбды на каждый объект генератора? Не в курсе как это точно работает, но питонячно! В современных плюсах подобное тоже выразимо, но это уже всё равно не уровень начинающего: понимание подобного что в плюсах, что в расте - признак не дебила.

fn main() -> Result<(), Box<dyn Error>> {

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

let mut is_sorted = false;

В Rust это выглядит НАДЁЖНЕЕ чем в С++, потому что заставили написать mut, чтобы сообщить, что это можно менять. В C++ это выглядит так же коротко в принципе: auto is_sorted = false;. Но в крестах ты пишешь const auto is_sorted = false; если надо конст и всё.

Посмотрим как пилятся сруктуры в расте:

pub struct CodeStats {
    /// The blank lines in the blob.
    pub blanks: usize,
    /// The lines of code in the blob.
    pub code: usize,
    /// The lines of comments in the blob.
    pub comments: usize,

Блин, в C++ же проще:

struct CodeStats {
  // The blank lines in the blob.
  usize blanks;
  // The lines of code in the blob.
  usize code;
  // The lines of comments in the blob.
  usize comments;

Я потратил меньше кода в крестах. Мне не надо писать pub напротив каждого поля, я могу его вынести в начало. Плюс, struct в крестах - это по-дефолту всё pub, а class - по-дефолту всё private - можно регулировать приватовость всех полей сразу просто выбором слова, которым объявлять структуру.

Что бесит в Rust: тип в конце. Но в языках, где важна производительность, люди любят подумать про memory layout - «как всё лежит в памяти» и посмотреть в первую очередь на типы всего, что лежит в структуре: какой тип рядом с каким, как это выровняется, например. Понятно, что в структуру в таких случаях данные пихают не по выравниванию, а «что рядом с чем потребляется процом», чтобы «нужное вместе» в одну кеш-линию, поэтому важнее будут имена полей, чем типы. Но всё-таки хочется «от общего к частному»: сначала видеть ЧТО ЭТО В ПРИНЦИПЕ (какой у этого тип), а уже потом как оно называется. Условно, мне хочется в «семантике общения» ситуацию «это собака, её зовут Вася и это тоже собака, и гоша», а не наоборот: «это Вася, а ещё он собака, а это петя и он собака». Я не хочу думать про имена, я хочу сначала схватывать суть уровня «так, тут у нас две псины, что они тут делают», а как они называются я потом разберусь)

pub fn summarise(&self) -> Self {

Об этом уже говорилось, в C++ тут будет меньше кода. А где тип аргумента, статически типизированные вы наши, йопт? В целом понятно, почему они заставили писать pub перед каждой функцией - чтобы тупорылый разраб сразу видел точно публичное оно или нет. А то в C++ напишут слово public: а дальше ряд функций и могут случайно написать функцию не в той секции и она случайно будет public – лучше пусть явно пишут! Но хз, это вкусовщина: мне приятнее организовывать всё именно как в плюсах: написать public и дальше у нас красиво пошёл публичный интерфейс. Нафиг мне pub в глаза пихать на каждый чих. Та же конструкция в C++: Self summarise(T &self) {. Опять же, уже говорилось: наличие & - раст оказался не таким уж простым, юзеру надо думать ссылка там или не ссылка? Где такая же простота, как в ссаном JS, что просто self написал и всё?

Давайте просто сюда посмотрим: https://github.com/sharkdp/fd/blob/master/src/filesystem.rs – в принципе да, всё читаемо, красиво, выразительно. map всякие там. Отсутствие скобок у if бесит конечно, ну ладно, в питонячке так же. Но в целом код на современных крестах выглядит абсолютно так же, может чуть меньше символов напечатать придётся. А где-то вместо map().blabla().bubu() будет несколько процедурных строк, потому что в именно стандартную библиотеку C++ не подвезли именно такой семантики, но она достижима в самом языке.

Посмотрим сюда: https://github.com/XAMPPRocky/tokei/blob/master/src/input.rs

Спецсимвольный perl-адок какой-то немного. Зачем так жить. Степень жести в районе строк 16-26 вообще ничем не способна привлечь юзера в сравнении с самыми мерзкими местами C++. Ясно, что это всё можно как-то объяснить. Точно так же «как-то» можно объяснить вот такой C++ код: https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/src/c++11/thread.cc#L235 - синтаксис обычный такой «сишный», просто вперемешку с макросами и большим количеством ___ в названиях переменных: в общем, оба фаната «как-то» объяснят ад в своих языках, простой мане, которая хотела «простой язык» оба этих места одинаково жопные.

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



Последнее исправление: lesopilorama (всего исправлений: 4)
Ответ на: комментарий от eao197

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

нет нельзя:

auto cing =  Disconnected{}.connect();
auto ced = cing.established();
ced = cing.established(); // ой, сломалось, по протоколу такое можно только 1 раз проделать
ced.successed_ping().drop();

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

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

А что должно быть? не отдельные ветки - это как? никакой компилтайм в будущее не может заглянуть и будет разделение в рантайме и именно 2 не 3 не 1.5, это суть выбора из булевского значения(и дело не в смысле термина пинг)

zurg
()
Ответ на: комментарий от Lrrr

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

lesopilorama
() автор топика
Последнее исправление: lesopilorama (всего исправлений: 1)
Ответ на: комментарий от zurg

он же вот так записал.

Disconnected{}.connect()
  .established()
  .successed_ping()
  .drop();

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

alysnix ★★★
()
Ответ на: комментарий от eao197

И чем оно лучше этого?

use std::thread;
use std::time::Duration;

#[derive(Debug, Clone, Copy)]
enum State {
    Disconnected,
    Connecting,
    Connected,
}

struct Connection {
    state: State,
}

impl Connection {
    fn new() -> Self {
        Connection { state: State::Disconnected }
    }

    fn connect(&mut self) {
        match self.state {
            State::Disconnected => {
                println!("Connecting...");
                self.state = State::Connecting;
                thread::sleep(Duration::from_secs(2)); // Эмуляция подключения
                self.state = State::Connected;
                println!("Connection established.");
            }
            State::Connecting => {
                println!("Already connecting.");
            }
            State::Connected => {
                println!("Already connected.");
            }
        }
    }

    fn ping(&self) {
        match self.state {
            State::Connected => {
                println!("Ping succeeded.");
            }
            _ => {
                println!("Cannot ping from current state.");
            }
        }
    }

    fn drop(&mut self) {
        match self.state {
            State::Connected | State::Connecting => {
                println!("Dropping connection.");
                self.state = State::Disconnected;
            }
            State::Disconnected => {
                println!("Already disconnected.");
            }
        }
    }
}


fn main() {
    let mut connection = Connection::new();

    connection.connect();
    connection.ping();
    connection.drop();
}
rtxtxtrx ★★
()
Последнее исправление: rtxtxtrx (всего исправлений: 1)
Ответ на: комментарий от zurg

нет нельзя:

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

Во-вторых, вы бы определились с тем, что вы хотите продемонстрировать: удобство Rust-а в декларативном описании КА или же такие фишки Rust-а, как «затенение» переменных (или как оно там называется официально) и деструктивное перемещение.

А что должно быть?

КА с внутренним состоянием и реакцией на внешние воздействия.

не отдельные ветки - это как?

У меня есть ощущение, что вы задачу КА пытаетесь заменить на другую задачу, а именно: у вас есть объект X у которого есть метод x, и после вызова метода x объект X должен измениться так, чтобы у него можно было вызывать только методы y и z, а метод x – нельзя.

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

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

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

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

Попробуйте представить этот ваш «КА» в виде объекта, который должен лежать в каком-то другом объекте (например, в объекте session), должен иметь какое-то внутреннее состояние (вроде дескриптора сокета, времени последней активности в сокете, буфер с остатками пока еще неразобранных данных) и должен реагировать на внешние воздействия, меняя свое состояние в зависимости от них.

Насколько полезным здесь окажется то же самое «затенение» переменных?

eao197 ★★★★★
()
Ответ на: комментарий от eao197

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

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

Во-вторых, вы бы определились с тем, что вы хотите продемонстрировать: удобство Rust-а в декларативном описании КА

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

КА с внутренним состоянием и реакцией на внешние воздействия.

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

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

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

Попробуйте представить этот ваш «КА» в виде объекта, который должен лежать в каком-то другом объекте (например, в объекте session), …

conn и есть такой «объект» , может храниться, передаваться, содержать что угодно, и всё это не тратя ресурсов на логику самого автомата,

и должен реагировать на внешние воздействия, меняя свое состояние в зависимости от них.

я и это показал(в отличие от вашего примитивного примера), но только это у вас вызывает странное ГЫГЫ вида «отдельные ветки для обработки успешного и неудачного пингов – это доставляет.» которое вообще заставляет сомневаться в вашей адекватности, как ещё в рантайме можно среагировать на событие булевского типа которое имитируется bool_fun()? В чём собственно гыгы?

«затенение» переменных

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

zurg
()
Ответ на: комментарий от zurg

Так может и сами потрудитесь прочитать тред выше где уже есть многие ответы

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

Неужели это настолько невероятная вещь?

вещает банальности игнорируя ответы

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

про декларативность я не заявлял это выше докополись что КА у меня визуально не в виде таблички(они почему-то связывают это с декларативностью)

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

и в обоих примерах это всё есть

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

И я ровно это продемонстрировал прямо по теме топика.

Нет, увы.

Что за наезды тогда?

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

conn и есть такой «объект» , может храниться, передаваться, содержать что угодно, и всё это не тратя ресурсов на логику самого автомата,

Ну тогда покажите как это будет работать, а то я не понимаю.

Вот, допустим, в C++ (как и в каком-нибудь Python-е, Java или D) у нас будет что-то вроде:

struct session {
  connection _conn; // connection -- это и есть КА со своим состоянием.
  ...
};

// Коллбэки которые где-то когда-то вызываются и воздействуют
// на содержимое session.
void on_incoming_data(session & s, data && d) {
  s._connection.push_event(incoming_data{std::move(d)});
  ...
}
void on_connected(session & s, connection_info && ci) {
  s._connection.push_event(connected{std::move(ci)});
  ...
}
void on_connection_lost(session & s) {
  s._connection.push_event(disconnected{});
  ...
}

У ваших же conn у каждого свой новый тип после каждой операции с эффектом деструктивного перемещения. Как вы этот conn поместите в session?

я и это показал(в отличие от вашего примитивного примера)

Нет. И как раз мой примитивный пример является тому илюсстрацией.

но только это у вас вызывает странное ГЫГЫ вида «отдельные ветки для обработки успешного и неудачного пингов – это доставляет.» которое вообще заставляет сомневаться в вашей адекватности

Пока что разговор с вами заставляет усомниться в том, что у вас есть опыт работы с КА. Больше похоже, что вы прочитали главу в Rust book-е и на этом все.

eao197 ★★★★★
()
Ответ на: комментарий от andalevor

В Паскаль превратился Algol-W, которым Хоар и Вирт пытались заменить Алгол-60. Но вместо этого комитет по разработке стандарта сделал еще один мертворожденный Алгол-68.

LongLiveUbuntu ★★★★★
()