LINUX.ORG.RU

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

 


0

7

Если 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)

Ответ на: комментарий от rumgot

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

Это если типов достаточно для выражения, потому что в С

struct point *body;
Может быть
struct point *aBody;
struct point *pBody;
А в С++ вместо a, p тебе придется записывать несколько десятков букв.

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

Потому что именно PL/1 широко известен как язык с такой кучей всякой всячины внутри, что не было даже компиляторов, которые поддерживали бы все фичи языка.

PL/0

Это что? Гугл говорит, что был такой язык от Вирта чисто для обучения и неприменимый для реальных программ. Если речь о нём, то какая связь?

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

Может быть

struct point *aBody;
struct point *pBody;

Может и в js так быть. Вопрос в том, а нафига оно так? Вот эти все:

a, p, s, cs, sz, i, d

и прочие префиксы сейчас не нужны и используются по инерции.

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

то какая связь?

Хотел пошутить. Типа почему начали с 1 в случае PL/1. А оказалось, что PL/0 существует.

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

Может и в js так быть.

Мы говорим про языки со статической типизацией, а не про js.

Вопрос в том, а нафига оно так?

Потому что без a/p непонятно, это указатель на массив или на единственный объект. Так же и с char*, это указатель на некий срез, или на строку с завершающим нулем?

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

Я полагаю, что у Вирта это тоже было чем-то вроде шутки, потому что PL/0 вышел лет через 10 после PL/1. Но, вроде как, именно PL/0 потом мутировал в паскаль.

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

Потому что без a/p непонятно, это указатель на массив или на единственный объект.

Так дай нормальное имя, типа:

SomeType* validator;
SomeType* validators;

struct point* body;
struct point* bodies;

Ну и что тут непонятно?

rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 3)
Ответ на: комментарий от MOPKOBKA

А что ты собираешься добавлять к char*?

А зачем к нему что-то добавлять? Какой кейс?

А к такому? Validators *validators;

Если тип назван Validators, то может там внутри уже список или вроде того, то зачем на него указатель? Ты хочешь создать список из списков? Назови типа validatorsGroups. А если тип назван Validators, а фактически это одна сущность, то вопрос к именованию типа.

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

А что ты собираешься добавлять к char*?

А зачем к нему что-то добавлять? Какой кейс?

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

Если тип назван Validators, то может там внутри уже список или вроде того, то зачем на него указатель?

Что бы не копировать структуру по стеку.

Назови типа validatorsGroups

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

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

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

Можно и без венгерской нотации обойтись, но так длиннее.

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

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

А зачем тебе короче? У тебя что монитор ЭЛТ 80x25? Или ты программируешь на смартфоне? Нафиг эти китайские грамоты в виде правил именования. Есть нормальное слово Validators - оно самодостаточно. А всякие aValidators, hValidators, xyuValidators - это как яйца кота, которые их он лижет, когда нечего делать.

Что бы не копировать структуру по стеку.

Еще раз: имя Validators намекает, что там уже список или массив. Например такой:

using Validators = Validator*;
rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 1)
Ответ на: комментарий от rumgot

Еще раз: имя Validators намекает, что там уже список или массив. Например такой:

struct Validators;

Ну пусть даже GList<Validator>, не копировать же его по стеку.

А зачем тебе короче?

Длинные названия читать неудобно.

MOPKOBKA ★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 3)
Ответ на: комментарий от MOPKOBKA

struct Validators; - это странно конечно, но если там семантически что-то одно, то и указатель может называться как одна сущность:

Validators validators;

Длинные названия читать неудобно.

Каракули читать неудобно.

Ну пусть даже GList, не копировать же его по стеку.

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

rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 3)
Ответ на: комментарий от rumgot

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

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

struct Validators; - это странно конечно, но если там семантически что-то одно, то и указатель может называться как одна сущность:

Считаю что это запутанно.

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

но если полей больше, то постоянное копирование это не очень здорово.

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

Считаю что это запутанно.

Ну а что тебе это дает? Все равно полный тип тебе префиксы не подскажут во всех случаях, например: pX - это указатель простой, умный, если умный, то какой из них и т.п.? sName - это std::string, QString или еще что? aValidator - это массив std::array, std::vector, простой массив или массив через сырой указатель? Что мне с того префикса толку? - хз.

А понимаю, что во времена 80x25 прогеры не могли себе позволить длинные имена и экономили место, кроме того подсветки синтаксиса не было, всплывающих подсказок и тогда действительно это помогало, но сейчас то что? Это как ездить на лошади, когда можно на авто.

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

Все равно полный тип тебе префиксы не подскажут

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

В венгерской нотации лучше записать idSome чем iSome, это дает больше информации. Или например lenSome > iSome. aObj > pObj. Но если тебе правда хочется отделить строки Qt от обычных, то ты можешь писать qsName, sName.

MOPKOBKA ★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 2)
Ответ на: комментарий от MOPKOBKA

Венгерская нотация не для этого

Для этого в том числе. Когда она применялась только к C, ее хватало на все типы.

то ты можешь писать qsName, sName

Мда… А как отделить std::string от std::u16string? usName? Каракули какие-то.

rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 2)
Ответ на: комментарий от rumgot

Для этого в том числе. Когда она применялась только к C, ее хватало на все типы.

Не совсем, всегда семантический приоритет выше, если нельзя его подобрать, то ставится тип, или если тип сам по себе важен. Например в функции HeapCreate() первый аргумент это DWORD flOptions, а не dwOptions.

Никто не пишет iID, пишут ID просто, или m_ID.

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

ее хватало на все типы

Максимум на типы ansi c. А ведь пользователи свои типы тоже заводят.

Мда… А как отделить std::string от std::u16string? usName? Каракули какие-то.

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

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

Не знаю Rust и возможно поэтому вижу здесь линейные переходы вместо таблицы. Что будет если сделать вызов Ping из состояния Disconnected?

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

ты опять бредишь. в скриптовых языках одна строка, одно число (и разницы между int и float нет так как их можно складывать), булев, null(nil, None). скалярных типов мало. не нужно гадать какой разновидности число или строка. + код пишут все-таки в ide. я не знаю как проще объяснить. отсутствие указания типов при инициализации не является проблемой, их бывает сложно в функциях из контекста понять, поэтому сначала были придуманы всякие jsdoc,phpdoc,pydoc, а потом тайпхинты.

# тут ясно что email строка
# так же ясно, что функция вернет булев
def is_email(email): ...

# тут ясно что функция обрежет строку и вернет новую,
# что s(tring) - это строка, l(ength) - длина
def cut(s, l): ...

# тут ясно что host строка, а port число, ведь иначе не бывает
def connect(host, port): ...


# а если же есть неопределенность, то можно использовать тайпхинты для подсказки редактору
def decode(input: str) -> bytes: ...

В js нету тайпхинтов, но можно их через комментари указать. IDE их парсят:

/**
 * Обрезает строку до указанной длины.
 *
 * @param {string} str - Исходная строка, которую нужно обрезать.
 * @param {number} maxLength - Максимальная длина строки после обрезания.
 * @param {boolean} [addEllipsis=false] - Добавлять ли многоточие в конце обрезанной строки.
 * @returns {string} - Обрезаная строка.
 *
 * @example
 * // Обрезаем строку до 10 символов
 * const truncated = truncateString('Это очень длинная строка', 10);
 * console.log(truncated); // Вывод: 'Это очень '
 *
 * @example
 * // Обрезаем строку до 10 символов и добавляем многоточие
 * const truncatedWithEllipsis = truncateString('Это очень длинная строка', 10, true);
 * console.log(truncatedWithEllipsis); // Вывод: 'Это очень…'
 */
function truncateString(str, maxLength, addEllipsis = false) {
    if (str.length <= maxLength) {
        return str;
    }

    let truncated = str.slice(0, maxLength);

    if (addEllipsis) {
        truncated += '…';
    }

    return truncated;
}

Но для JS отсутствие тайпхинтов не является проблемой, так как он в отличии от Python не является универсальным языком, а лишь обраьатывает формы на странице, где значения полей почти всегда СТРОКИ, которых ВСЕГО ОДИН ТИП (точно так же и в баше все строки). Поэтому и TypeScript не нужен.

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

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

Я не он, но с «мусорными» enum (компилируется у меня за ~141 мс):

struct Foo;

impl Foo {
    fn hi(&self) -> i64 {
        1
    }
}

struct Bar;

impl Bar {
    fn hi(&self) -> i64 {
        2
    }
}

enum Entity {
    Foo(Foo),
    Bar(Bar),
}

fn cb(entity: Entity) {
    match entity {
        Entity::Foo(_) => println!("foo"),
        Entity::Bar(_) => println!("bar"),
    }
}

fn f(x: i32) -> Entity {
    if x % 2 == 1 {
        Entity::Foo(Foo)
    } else {
        Entity::Bar(Bar)
    }
}

fn main() {
    let foo = f(1);
    let bar = f(2);
    
    match foo {
        Entity::Foo(ref foo) => assert_eq!(foo.hi(), 1), // foo.hi() should return 1
        _ => panic!("Expected Entity::Foo"),
    }

    match bar {
        Entity::Bar(ref bar) => assert_eq!(bar.hi(), 2), // bar.hi() should return 2
        _ => panic!("Expected Entity::Bar"),
    }

    cb(foo);
    cb(bar);
}

Без «мусорных» enum (за ~148 мс):

trait Hi {
    fn hi(&self) -> i64;
}

struct Foo;

impl Hi for Foo {
    fn hi(&self) -> i64 {
        1
    }
}

struct Bar;

impl Hi for Bar {
    fn hi(&self) -> i64 {
        2
    }
}

fn cb(s: &dyn Hi) {
    if s.hi() == 1 {
        println!("foo");
    } else {
        println!("bar");
    }
}

fn f(x: i32) -> Box<dyn Hi> {
    if x % 2 == 1 {
        Box::new(Foo)
    } else {
        Box::new(Bar)
    }
}

fn main() {
    let foo = f(1);
    let bar = f(2);

    assert_eq!(foo.hi(), 1); // foo.hi() should return 1
    assert_eq!(bar.hi(), 2); // bar.hi() should return 2

    cb(&*foo);
    cb(&*bar);
}

Твой пример с добавлением «аналогичного» main компилируется за ~741 мс. Бенчмарк делал в hyperfine.

Более понимающие люди поправят код, а то я не настоящий слесарь.

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

Не совсем, всегда семантический приоритет выше

Ага, когда собственные правила мешают, они идут лесом, да? Это лишняя демонстрация ненужности.

А ведь пользователи свои типы тоже заводят.

Ну все равно потом они через указатели используются.

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

Вот, ты начинаешь понимать. Без венгерской.

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

Ага, когда собственные правила мешают, они идут лесом, да? Это лишняя демонстрация ненужности.

Ты что то не так понял. Правила такие, что семантическая приставка выше.

Ну все равно потом они через указатели используются.

Да, но в WinAPI никто не использует название pHANDLESome, там hSome.

Вот, ты начинаешь понимать. Без венгерской.

sName, sNameUtf16

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

тайпхинтов

Про них вообще речи не было. Тебя кто это просил писать? Голоса в голове?

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

*Приручаешь :-). Потому что иначе сам потом понять не можешь, что где.

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

Проблема там такова: обезьяны не могут на них писать.

Удел скрпитобогов — быть непонятыми.

А, понятно. Ну, бог, давай.

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

Да, но в WinAPI никто не использует название pHANDLESome, там hSome.

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

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

sName, sNameUtf16

Какая тут ценность этого s - я хз.

- sName
- Число или строка, число или строка !? Ну вообще нет никаких предположений !!!
- Смотрите на s!
- Точно! Да это же строка!!! Ура!!!
rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 1)
Ответ на: комментарий от Jini

Да, отработает с логическим багом, но это именно прямое переписывание в лоб плюсового варианта. Такая версия:

struct Conn<State> {
    st: State
}
struct Disconnected;
struct Connecting;
struct Connected;

impl<State> Conn<State> {
    pub fn new()-> Conn<Disconnected> {
        Conn{ st: Disconnected }
    }
}
impl Conn<Disconnected> {
    pub fn connect(self) -> Conn<Connecting> {
        est();
        Conn{ st: Connecting }
    }
}
impl Conn<Connecting> {
    pub fn estable(self) -> Conn<Connected> {
        Conn{ st: Connected }
    }
}
impl Conn<Connected> {
    pub fn ping(self, valid: bool) -> Conn<Connected> {
        if valid { reset() };
        Conn{ st: Connected }
    }
    pub fn timeout(self) -> Conn<Connecting> {
        est();
        Conn{ st: Connecting }
    }
    pub fn disconnect(self) -> Conn<Disconnected> {
        close();
        Conn{ st: Disconnected }
    }
}
pub fn main() { 
    let conn = Conn::<Disconnected>::new();
    //let conn = conn.ping(true);  // ошибка компиляции
    let conn = conn.connect();
    let conn = conn.estable();
    let conn = conn.ping(true);
    let conn = conn.disconnect();
 
    assert!( size_of::<Conn<Connected> >() == 0);
}

https://godbolt.org/z/1qqo64MsT и вот это в плюсах повторить нельзя, по крайней мере практически. Оно статически проверяет правильность последовательности переходов состояний, плюс чистый зерокост и по тактам и по памяти, в плюсах тоже зерокост можно, но последовательность придётся проверять в рантайме, в сишке вообще всё в рантайм уедет (ну или на CVE.org) И это всё ещё изкоробочный раст, без библиотек

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

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

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

и оно переходит, в примере ping переводит в себя

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

вот это в плюсах повторить нельзя

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

что тут волшебного вообще?

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

В плюсах так или иначе остаётся доступным предыдущее состояние, которое снова можно дёрнуть и сломать автомат, и даже move не гарантирует недоступность, а растовый гарантирует и это однозначно выявляется в компилтайме. «Волшебная» тут аффинная типизация, вот тут лысый от хачкеля показывает пальцем и… сочуствует плюсовикам которые пытаются запилить линейные типы (родственные аффинным) https://www.youtube.com/watch?v=6fcEIPCa64c но выходит не очень

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

В плюсах так или иначе остаётся доступным предыдущее состояние,

в плюсах такой рустовый трюк - «затенение» делается через union. верней std::variant, и присваивание деструктуриует старое значение.

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

вопрос только - куда оно заведет, ибо «затенение» источник багов - однозначно.

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

https://godbolt.org/z/1qqo64MsT и вот это в плюсах повторить нельзя, по крайней мере практически.

Это и не нужно повторять вообще нигде, ибо кроме кроме форумной писькометрии у этого примера нет никакой иной ценности.

Чтобы хоть какая-то ценность обозначилась попробуйте сделать так, чтобы ping возвращал либо Connected, либо Disconnected в зависимости от входного параметра valid. А то сейчас у вас вышел какой-то уродец, а не КА – вы в ping-е инициируете reset (т.е. сброс соединения), но возвращаете состояние Connected.

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

Более понимающие люди поправят код, а то я не настоящий слесарь.

Боюсь, вы недостаточно хорошо знаете C++, чтобы уловить смысл исходного примера на C++.

Вот в таком виде он будет более очевиден:

#include <iostream>
#include <string>

using namespace std;

struct foo { int hi() { return 1; } };
struct bar { std::string hi() { return "two"; } };

void cb(auto s) {
  if constexpr (is_same_v<decltype(s.hi()), int>) {
    cout << "foo -> " << s.hi();
  } else {
    cout << "bar -> " << s.hi();
  }
}

void f(int x) {
  if (x % 2 == 1) {
    cb(foo{});
    return;
  }
  cb(bar{});
}

int main() {
    f(2);
}

https://wandbox.org/permlink/uhgheBUcg1CqTBwJ

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

доказывай что в js нужна типизация

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

rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 2)
Ответ на: комментарий от rumgot

Доказывай что венгерскую нотацию хоть кто-то применял в js/python. В крестах там она для поинтеров везде. Актуально-неактуально. Раз пишут, значит так надо. Но зачем оно в языках где этих самых указателей нет

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

В крестах там она для поинтеров везде.

Уже давным-давно не везде.

Может быть в каких-то проектах, тесно завязанных на виндовый мир и MFC/WTL оно и есть, но за пределами этого мира венгерка в C++ – это крайне редкое явление. Можете сами порыться в исходниках Boost-а, Abseil, Folly и пр.

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

Доказывай что венгерскую нотацию хоть кто-то применял в js/python

Боже ж ты мой. Где я так говорил?

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

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

Там написано:

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

Что было дополнительным ответом к твоему:

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

У id число всегда, status — строка, потому как statusCode число, а result - это не аргумент функции, а результат, который должен быть понятен из названия самой функции.

и также к твоему же:

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

Такого не бывает. Ты всегда понимаешь, что age - это число, username и password - строки, а isAuthed - тип для буллинга.

Где ты привел некие свои правила относительно типов у переменных с такими именами в js.

Т.е. ты сам придумал некие правила именования, я тебе возразил (ну точнее сделал вывод о том, что то, что ты привел является некоторыми правилами а-ля венгерка). А теперь ты меня просишь обосновать необходимость венгерки в js и привести примеры. Ты причину со следствием различаешь?

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

вы в ping-е инициируете reset

Это не я инициирую, а автор исходного плюсового примера и для демонстрации это не важно. Но, допустим, автомат зависит от флага в рантайме тогда автомат просто разобъётся на ветки(а как иначе?):

 struct Conn<State> {
    st: State
}
struct Disconnected;
struct Connecting;
struct Connected;

impl<State> Conn<State> {
    pub fn new()-> Conn<Disconnected> {
        Conn{ st: Disconnected }
    }
}
impl Conn<Disconnected> {
    pub fn connect(self) -> Conn<Connecting> {
        est();
        Conn{ st: Connecting }
    }
}
impl Conn<Connecting> {
    pub fn estable(self) -> Conn<Connected> {
        Conn{ st: Connected }
    }
}
impl Conn<Connected> {
    pub fn ping_true(self) -> Conn<Connected> {
        reset();
        Conn{ st: Connected }        
    }
    pub fn ping_false(self) -> Conn<Disconnected> {
        close();
        Conn{ st: Disconnected }        
    }
    pub fn timeout(self) -> Conn<Connecting> {
        est();
        Conn{ st: Connecting }
    }
    pub fn disconnect(self) -> Conn<Disconnected> {
        close();
        Conn{ st: Disconnected }
    }
}
pub fn main() { 
    let conn = Conn::<Disconnected>::new();
    let conn = conn.connect();
    let conn = conn.estable();
    if bool_fun() { 
        branch_true(conn);
    } else { 
        let conn = conn.ping_false(); 
    };
    assert!( size_of::<Conn<Connected> >() == 0);
}
fn branch_true(conn: Conn<Connected>) {
   // ну и вообще муваем состояние  глубоко настолько, насколько надо отслеживать корректность переходов  
    let conn = conn.ping_true();
    let conn = conn.disconnect();
}

https://godbolt.org/z/ET1vPKoc3

Это и не нужно повторять вообще нигде

Эээ, штука (typestate) довольно давно и охрененно практичная, осбенно в эмбедеде: растовые HALы сильно на это завязаны, и в плюсах как вот здесь https://www.youtube.com/watch?v=MhTg9Jnwmms&t=1659s чуваки хорошо так упоролись шаблонной магией чтобы впихнуться в несколько сот байт ОЗУ.

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

Это не я инициирую, а автор исходного плюсового примера и для демонстрации это не важно.

Для демонстрации чего не важно? Того, что не имеет отношения к реальному миру?

Ну тогда ОК, вопросов нет.

pub fn main() { 
    let conn = Conn::<Disconnected>::new();
    let conn = conn.connect();
    let conn = conn.estable();
    if bool_fun() { 
        branch_true(conn);
    } else { 
        let conn = conn.ping_false(); 
    };
    assert!( size_of::<Conn<Connected> >() == 0);
}

Сразу закапывайте такие КА.

Эээ, штука (typestate) довольно давно и охрененно практичная

Именно такая как вы показали? Сильно сомневаюсь.

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

Оно УЖЕ используется, реальнее некуда, по крайней мере реальнее лоровских тредиков, вот сюда можно посмотреть https://docs.rust-embedded.org/book/static-guarantees/design-contracts.html, а в плюсах такое используется, я подозреваю, ещё со времён когда Александреску торкнуло

Сразу закапывайте такие КА.

что не так то, объективные логические претензии будут? для показывания ПРИНЦИПА более чем достаточно, полноценный промышленный протокол что-ли надо реализовать? для лоровского срачика? да нафиг надо

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

Оно УЖЕ используется

Вы, видимо, не дали себе труда прочитать что вам написали, а именно:

Именно такая как вы показали?

Показанный вами КА не имеет никакой практической пользы кроме как пихнуть пример кода на LOR-е.

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

Да. КА – это объект с каким-то своим состоянием, в который пихают внешние воздействия и который меняет (или нет) свое состояние под влиянием этих воздействий, и реагирует на воздействия исходя из своего состояния.

У вас же просто меняется набор объектов, у каждого из которых свой интерфейс. Для этого не нужно вообще никаких огородов городить, можно простыми структурами пользоваться. Что-то вроде (пример условный):

struct Disconnected {
  Connecting connect();
};
struct Connecting {
  Disconnected failed();
  Connected established();
};
struct Connected {
  Disconnected failed_ping();
  Connected successed_ping();
  Disconnected drop();
};

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

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

ЗЫ. Я не пытаюсь сказать, что исходный пример на C++ огонь и именно так и должны выглядеть КА в коде. Но если уж вы начали доказывать, что Rust круче, так покажите что он реально круче.

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

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

bread
()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.