Вот Rust гордится своим Option для всего — мол по умолчанию можно не опасаться, что будет NRE или попытка обратиться через nullptr.
А ведь вместо Option — гораздо удобнее и понятнее символ ? а так же сопутствующие ему — ! ?? и пр. — что уже фактически стало стандартом — используется и в C# и в Dart совершенно одинаково и интуитивно можно сказать понятно.
Здравствуйте, Shmj, Вы писали:
S>Вот Rust гордится своим Option для всего — мол по умолчанию можно не опасаться, что будет NRE или попытка обратиться через nullptr.
S>А ведь вместо Option — гораздо удобнее и понятнее символ ? а так же сопутствующие ему — ! ?? и пр. — что уже фактически стало стандартом — используется и в C# и в Dart совершенно одинаково и интуитивно можно сказать понятно.
S>Ведь Rust отстает, получается.
Сравнение несравнимого на мой взгляд.
Одно дело возможность доступа по потенциально невалидной ссылке,
а другое удобство запись операции:
if (a != null)
return a.something;
else
return null;
В C# же "." не запрещен. Option это аналог анотации [CanBeNull] ,
которую проверяет не ReSharper, а компилятор.
Здравствуйте, Shmj, Вы писали:
S>Вот Rust гордится своим Option для всего — мол по умолчанию можно не опасаться, что будет NRE или попытка обратиться через nullptr.
Это ведь семантика
S>А ведь вместо Option — гораздо удобнее и понятнее символ ? а так же сопутствующие ему — ! ?? и пр. — что уже фактически стало стандартом — используется и в C# и в Dart совершенно одинаково и интуитивно можно сказать понятно.
А это синтаксис, он действительно не очень
S>Ведь Rust отстает, получается.
Да, с точки зрения синтаксических возможностей раст довольно бедный язык. ? оператор есть, а ?? нет. И ! нет, надо везде писать .unwrap().
Нет оператора is как C#, предлагается через громоздкий if let делать.
А еще нет перегрузок функций, дефолтных параметров, нет функций с переменным числом параметров.
Операции с указателями крайне неудобны, так как операция разыменовывания имеет меньший приоритет и нет оператора -> как в C. Вроде как сделано для того, чтобы люди меньше писали unsafe, но некоторые вещи сложно эффективно выразить в safe.
Это все чисто синтаксические недостатки.
Есть еще и проблемы в семантике: работа с Error, пробрасывание ошибок и стектрейсы для error, отсутствие наследования (которое в каждом втором фреймфорке переизобретено). Отсутствие стандартного метода работы с метаданными и вообще отсутствие высокоуровневых концепций в стандартной библиотеке, async прибитый к конкретному движку.
Вроде есть макросы для борьбы со всеми неудобствами, но макросы зачастую создают проблем больше, чем решают.
Здравствуйте, gandjustas, Вы писали:
S>>Ведь Rust отстает, получается. G>Да, с точки зрения синтаксических возможностей раст довольно бедный язык. ? оператор есть, а ?? нет. И ! нет, надо везде писать .unwrap().
"unwrap" как раз не надо везде писать, он нужен для тестов или "main" функции,
"unwrap" в коде функции как раз антипатерн.
G>Нет оператора is как C#, предлагается через громоздкий if let делать. G>А еще нет перегрузок функций, дефолтных параметров, нет функций с переменным числом параметров.
Я бы назвал это как раз достоинствами. В областях где нужна надежность,
код который можно только однозначно понять очень важен.
И неявная передача дополнительного параметра (дефолтные параметры),
или наличие функций с одинаковыми именами, но потенциально разным поведением в одном контексте (перегрузка)
это однозначно плохо.
Из-за совместимости с С функции с переменным числом параметров как раз поддерживаются:
G>Операции с указателями крайне неудобны, так как операция разыменовывания имеет меньший приоритет и нет оператора -> как в C. Вроде как сделано для того, чтобы люди меньше писали unsafe, но некоторые вещи сложно эффективно выразить в safe.
Указатель можно превратить в ссылку одним "unsafe", а потом обращаться ко всем полям через ".",
и эффективность от этого никак не пострадает.
G>Есть еще и проблемы в семантике: работа с Error, пробрасывание ошибок и стектрейсы для error,
На мой взгляд, очерненный C++, лучше все-таки явные ошибки, а не исключения,
которые заставляют все время сомневаться, а вдруг вот здесь будет исключение,
а что при этом сломается.
G>отсутствие наследования (которое в каждом втором фреймфорке переизобретено).
Наследование-то есть. Но наследование поведения, а ООП как раз о поведении.
Наследования данных действительно нет, но для ООП это и не нужно, просто так исторически
сложилось, что классы наследовали и поведение и данные.
G>Отсутствие стандартного метода работы с метаданными и вообще отсутствие высокоуровневых концепций в стандартной библиотеке,
Здесь не понял о чем речь.
G>async прибитый к конкретному движку.
Это не правда, "async" в embedded применяется,
там не просто "tokio" нет, там даже стандартной библиотеки не используется,
а "async" прекрасно работает. Там же по сути концепций раз два и обчелся: Waker, Context и Future.
И их может кто угодно использовать, как это может считаться "прибитым к конкретному движку" непонятно
Здравствуйте, Zhendos, Вы писали:
Z>На мой взгляд, очерненный C++, лучше все-таки явные ошибки, а не исключения, Z>которые заставляют все время сомневаться, а вдруг вот здесь будет исключение, Z>а что при этом сломается.
Здравствуйте, Zhendos, Вы писали:
Z>Здравствуйте, gandjustas, Вы писали:
S>>>Ведь Rust отстает, получается. G>>Да, с точки зрения синтаксических возможностей раст довольно бедный язык. ? оператор есть, а ?? нет. И ! нет, надо везде писать .unwrap().
Z>"unwrap" как раз не надо везде писать, он нужен для тестов или "main" функции, Z>"unwrap" в коде функции как раз антепатерн.
Например я пишу приложение, которое pdf файлы разбирает на кусочки. Если друг не получилось прочитать или записать файл, то программа должна упасть. У меня нет других вариантов обработки этой ошибки. Причем упасть желательно со стектрейсом. Почему в этом случае я не должен использовать unwrap?
G>>Нет оператора is как C#, предлагается через громоздкий if let делать. G>>А еще нет перегрузок функций, дефолтных параметров, нет функций с переменным числом параметров.
Z>Я бы назвал это как раз достоинствами. В областях где нужна надежность, Z>код который можно только однозначно понять очень важен.
Твоя вера сильна.
Z>И неявная передача дополнительного параметра (дефолтные параметры), Z>или наличие функций с одинаковыми именами, но потенциально разным поведением в одном контексте (перегрузка) Z>это однозначно плохо.
Говорят, если белое долго называть черным, то некоторые даже поверят.
Z>Из-за совместимости с С функции с переменным числом параметров как раз поддерживаются: Z>
Ты же не можешь в расте сделать свою функцию с переменным числом параметров. Даже если ты её потом экспортируешь.
G>>Операции с указателями крайне неудобны, так как операция разыменовывания имеет меньший приоритет и нет оператора -> как в C. Вроде как сделано для того, чтобы люди меньше писали unsafe, но некоторые вещи сложно эффективно выразить в safe.
Z>Указатель можно превратить в ссылку одним "unsafe", а потом обращаться ко всем полям через ".",
У тебя есть указатель на структуру А, в которой есть поле — указатель на B, напиши как будет выглядеть обращение к этому полю.
Z>и эффективность от этого никак не пострадает.
Аминь
G>>отсутствие наследования (которое в каждом втором фреймфорке переизобретено). Z>Наследование-то есть. Но наследование поведения, а ООП как раз о поведении.
То что в расте вы называете наследованием поведение — это экстеншн-методы в извращенной форме.
Z>Наследования данных действительно нет, но для ООП это и не нужно, просто так исторически Z>сложилось, что классы наследовали и поведение и данные.
Ага, не нужно. Открываем доку https://rust-cli.github.io/book/tutorial/cli-args.html прямо с оф сайта видим:
use clap::Parser;
/// Search for a pattern in a file and display the lines that contain it.
#[derive(Parser)]
struct Cli {
/// The pattern to look for
pattern: String,
/// The path to the file to read
path: std::path::PathBuf,
}
fn main() {
let args = Cli::parse();
}
Упс, наследование поведения через макросы. Даже ооп как такового нет, а наследование есть.
Для ооп нужен еще полиморфизм подтипов, а его нет даже с наследованием. Его реализация на макросах в полной мере невозможна, но в ограниченном варианте есть во многих библиотеках.
G>>Отсутствие стандартного метода работы с метаданными и вообще отсутствие высокоуровневых концепций в стандартной библиотеке, Z>Здесь не понял о чем речь.
Где в стандартной библиотеке json, http client, http server, unicode. Так чтобы их не стесняясь использовать и в создании своей библиотеки?
G>>async прибитый к конкретному движку. Z>Это не правда, "async" в embedded применяется, Z>там не просто "tokio" нет, там даже стандартной библиотеки не используется,
Не понимаю твою мантру. Ты хочешь сказать что можно написать веб-сервис, а потом например заменить tokio на async-std, не переписывая прикладной код?
Z>а "async" прекрасно работает. Там же по сути концепций раз два и обчелся: Waker, Context и Future.
Концепций может и мало, а их реализаций много. И они несовместимы между собой. И нет какой-то типовой, всклоченной в стандартную библиотеку.
Z>И их может кто угодно использовать, как это может считаться "прибитым к конкретному движку" непонятно
В этом и проблема.
Не спора ради.
Я встречал прямо противоположное мнение: синтаксис должен быть максимально простым. Если какие-то операции ну очень часто встречаются (тот же error bubble wrapping в отсутствие exceptions), тогда надо делать синтаксис (и получатся все те же, хаха, exceptions).
Так же и с Rust: если в него добавить все эти "??", "!" и прочие удобства, будет существенно сложнее понимать написанный код (т.к. он будет более condensed). А также сложнее будет изначально выучить язык. Я вот до сих пор в C# путаюсь с этими всеми символами.
Здравствуйте, SkyDance, Вы писали:
SD>Я встречал прямо противоположное мнение: синтаксис должен быть максимально простым.
Видимо от тех кто пишет на Го
Им же кайф все время писать
res, err := f()
if err != nil {
return nil,err
}
(кстати идо сих пор не могу запомнить чем := отличается от просто =)
SD>Если какие-то операции ну очень часто встречаются (тот же error bubble wrapping в отсутствие exceptions), тогда надо делать синтаксис (и получатся все те же, хаха, exceptions).
В Rust сделали оператор ? чтобы не писать как в Go. При этом сделали не сразу, кажись после 1.0 добавили.
А вот до операторов ! вместо .unwrap() и ?? (||) вместо .or_else() еще не додумались.
SD>Так же и с Rust: если в него добавить все эти "??", "!" и прочие удобства, будет существенно сложнее понимать написанный код (т.к. он будет более condensed).
Я дважды проходил через это:
Сначала было в C#, когда ввели ?., ?? и ??=.
Потом такое же ощущение поймал, когда изучал zig, там обилие ?, ! и * в разных местах сильно смущает.
Сначала непонятно и непривычно, но стоит один раз написать программу с этими операторами и становится все сильно проще.
SD>А также сложнее будет изначально выучить язык. Я вот до сих пор в C# путаюсь с этими всеми символами.
В C# не так много символов чтобы путаться, странно что это вызывает проблемы.
Здравствуйте, Zhendos, Вы писали:
Z>Указатель можно превратить в ссылку одним "unsafe", а потом обращаться ко всем полям через ".", Z>и эффективность от этого никак не пострадает
Ага, иногда эффективность даже может повыситься, если компилятору покажется, что этот указатель не надо читать повторно (а на самом деле надо). И тогда после оптимизации программа поменяет поведение. С unsafe надо очень хорошо думать.
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
G>Видимо от тех кто пишет на Го G>Им же кайф все время писать <error bubble wrap>
Я же специально написал: error bubble wrapping — тот самый случай, когда exceptions переизобретаются в очередной раз (и в очередной раз криво, т.к. теряется стек, и непонятно, где же возникла исходная ошибка, — nil и nil, угадывай сам).
Но то я — противник tuple return (ибо считаю, что для читаемости функция должна всегда возвращать значение, а все ошибки должны идти по другому каналу, т.е. exceptions). Для фанатов противоположного подхода нужно не просто tuple типа {ok, Ret} и {error, Reason}, а полновесные {error, func_name, [Arg1, Arg2], line_no, {caused_by, inner_func_name, [Arg1], line_no1, {caused_by, ...}}}}}}. Ну то есть тот же самый стек, только не в виде exception, а прямо в коде возврата.
Для такой цели, разумеется, нужны те самые exceptions. Пусть и в таком вот explicit виде, когда стек едет рядом с return value.
G>(кстати идо сих пор не могу запомнить чем := отличается от просто =)
О чем я и писал: не всегда сразу очевидно, где нужно ?, где ??, и нет ли какого-нибудь ?| (ведь нет же, а?)
G>В Rust сделали оператор ? чтобы не писать как в Go. При этом сделали не сразу, кажись после 1.0 добавили.
Именно это и есть правильный подход. Не сразу все подряд лепить, а тщательно выбирать, что нужно и что не очень.
G>А вот до операторов ! вместо .unwrap() и ?? (||) вместо .or_else() еще не додумались.
Погоди, C# не сразу строился.
G>В C# не так много символов чтобы путаться, странно что это вызывает проблемы.
А кто чуть выше написал вот это: не могу запомнить чем := отличается от просто =
Здравствуйте, SkyDance, Вы писали:
G>>Видимо от тех кто пишет на Го G>>Им же кайф все время писать <error bubble wrap>
SD>Я же специально написал: error bubble wrapping — тот самый случай, когда exceptions переизобретаются в очередной раз (и в очередной раз криво, т.к. теряется стек, и непонятно, где же возникла исходная ошибка, — nil и nil, угадывай сам).
А чем этот случай лучше чем "do or die" или "мамой клянусь" оператор? Или "value or else" ? Они все примерно в том же контексте используются
G>>(кстати идо сих пор не могу запомнить чем := отличается от просто =)
SD> SD>О чем я и писал: не всегда сразу очевидно, где нужно ?, где ??, и нет ли какого-нибудь ?| (ведь нет же, а?)
Я не пишу на Go, поэтому запомнить не могу. А на C# пишу и не испытываю проблем со чтением всех современных операторов.
G>>В Rust сделали оператор ? чтобы не писать как в Go. При этом сделали не сразу, кажись после 1.0 добавили. SD>Именно это и есть правильный подход. Не сразу все подряд лепить, а тщательно выбирать, что нужно и что не очень.
А с другой стороны есть zig, где сразу все значки добавили и выглядят он там органично и непротиворечиво.
G>>В C# не так много символов чтобы путаться, странно что это вызывает проблемы. SD>А кто чуть выше написал вот это: не могу запомнить чем := отличается от просто =
Фишка в том, что в C# в разных контекстах используется ?. и ?? не получится заменить один на дугой и получить код похожий на валидный. А в го с = и := — вполне.
G>Фишка в том, что в C# в разных контекстах используется ?. и ?? не получится заменить один на дугой и получить код похожий на валидный.
А как бы Вы предложили реализовать оператор ?? в Rust? Насколько я понимаю, Option — это обычный enum, и у значения None нет какой-то особой поддержки на уровне языка, в отличии от null в C#.
Так же, насколько я понимаю, в идиоматическом Rust операторы присваивания встречаются редко, соответственно и потребность в ?? тоже ниже.
Оператор ! уже занят, для указания типов, которые не возвращают значения. Да и в языке просто нет нулевых ссылок.
S>В C# по умолчанию выдает warning, но можно сделать чтобы тоже не давало скомпилировать вот так:
В теории да, но на уже существующем большом проекте сделать будет затруднительно.
Можно ли считать такой код идеоматическим? Можно ли рассчитывать, что вся команда готова и умеет писать в подобном стиле?
Здорово, что в C# появляются такие возможности! Но используются они в продакшене, кажется, лишь энтузиастами и передовыми командами.
На Rust же просто не сможешь писать иначе.
Здравствуйте, Быдлокодер, Вы писали:
Б>Здравствуйте, gandjustas, Вы писали:
G>>Фишка в том, что в C# в разных контекстах используется ?. и ?? не получится заменить один на дугой и получить код похожий на валидный.
Б>А как бы Вы предложили реализовать оператор ?? в Rust? Насколько я понимаю, Option — это обычный enum, и у значения None нет какой-то особой поддержки на уровне языка, в отличии от null в C#.
Как сокращение для unwrap_or_else, то есть вместо opt.unwrap_or_else(|| value) писать opt ?? value
Б>Оператор ! уже занят, для указания типов, которые не возвращают значения. Да и в языке просто нет нулевых ссылок.
Основная проблема не типах, а в том, что ! используется в макросах.
Хотя было бы неплохо писать opt! вместо opt.unwrap() особенно в цепочках выражений.
Макросы я бы заменил на символ @ в началае.
G>А чем этот случай лучше чем "do or die" или "мамой клянусь" оператор?
do or die получается само собой при unhandled exception. Превратить в "or die" можно что угодно, это несложно. В обратную сторону же приходится колхозить.
G>Я не пишу на Go, поэтому запомнить не могу. А на C# пишу и не испытываю проблем со чтением всех современных операторов.
Мне приходится писать на всем подряд, от Erlang до Javascript. Читать — на еще бОльшем количестве языков. Держать в памяти, где в каком языке какие хитрые операторы, не получается. Что-то из памяти всегда выскакивает (как, к примеру, постоянно забываю про named arguments в C#).
G>А с другой стороны есть zig, где сразу все значки добавили и выглядят он там органично и непротиворечиво.
Это пока не появится еще один паттерн, под который придется добавлять очередной значок (и там уже снова начнется колхоз).
G>Фишка в том, что в C# в разных контекстах используется ?. и ?? не получится заменить один на дугой и получить код похожий на валидный.
Я скорее не про "писать", а про "читать". Мне читать надо много больше, чем писать.
Здравствуйте, Shmj, Вы писали:
S>А ведь вместо Option — гораздо удобнее и понятнее символ ? а так же сопутствующие ему — ! ?? и пр. — что уже фактически стало стандартом — используется и в C# и в Dart совершенно одинаково и интуитивно можно сказать понятно.
Здравствуйте, Shmj, Вы писали:
S>Ведь Rust отстает, получается.
конкретно тут отстаёт. Но надо учесть, что указатели в Rust встречаются куда реже чем в C# и Java и для их обработки не надо вводить синтаксический сахар. Тот же проброс ошибок — явление более частое, так что довольно быстро озаботились оператором ?. Вы приведите конкретный код, где вам бы хотелось применить элвис-оператор и вам расскажут как это идиоматично переписать на Rust. Лично я потребности в таком операторе не испытываю (хотя на шарпе часто даже злоупотребляю).
Здравствуйте, SkyDance, Вы писали:
G>>А чем этот случай лучше чем "do or die" или "мамой клянусь" оператор?
SD>do or die получается само собой при unhandled exception.
Это когда есть в языке исключения. А когда это раст, то там паттерн "or die" очень нужен.
SD>Превратить в "or die" можно что угодно, это несложно.
Проблема исключительно в синтаксисе
Вот пример кода на раст:
let ext = path.extension().unwrap().to_str().unwrap();
let path = path.with_extension(format!("{}.{}", page_index, ext));
doc.save(path).unwrap();
На три строки три .unwrap(), который реализует паттерн or_die? и это еще лайтово, бывает по три .unwrap на строку.
Гораздо лучше смотрелось бы так:
let ext = path.extension()?!.to_str()?!;
let path = path.with_extension(format!("{}.{}", page_index, ext));
doc.save(path)?!;
Где ?! — это сахар для .unwrap(), чтобы не было коллизий с макросами.
G>>Я не пишу на Go, поэтому запомнить не могу. А на C# пишу и не испытываю проблем со чтением всех современных операторов.
SD>Мне приходится писать на всем подряд, от Erlang до Javascript. Читать — на еще бОльшем количестве языков. Держать в памяти, где в каком языке какие хитрые операторы, не получается. Что-то из памяти всегда выскакивает (как, к примеру, постоянно забываю про named arguments в C#).
В любом случае надо знать семантику языка и библиотек чтобы читать. Не бывает так, что язык легко читается без некоторого опыта написания на нем.
G>>А с другой стороны есть zig, где сразу все значки добавили и выглядят он там органично и непротиворечиво. SD>Это пока не появится еще один паттерн, под который придется добавлять очередной значок (и там уже снова начнется колхоз).
Ну собственно так всегда и происходит, все языки так живут. Наоборот пытаясь добавить все значки на старте скорее всего получится мусор. В том же расте часть знаков, которые были в 1.0 поубирали, а другие добавили.
G>>Фишка в том, что в C# в разных контекстах используется ?. и ?? не получится заменить один на дугой и получить код похожий на валидный. SD>Я скорее не про "писать", а про "читать". Мне читать надо много больше, чем писать.
Чем меньше букв написано, тем проще читать в итоге. Знать язык все равно надо.