Комментарии 74
Изучая чего-нибудь…
Шаг первый: включить капитана очевидность
Шаг второй: использовать только свой "богатый" опыт
Шаг третий: встать в ступор от количества непонятного
Шаг четвертый: обозвать всех "идиотами"
Шаг пятый: озвучить Всем! свое мнение…
Шаг шестой: пойти искать, что-то еще более непонятное!
Вы верно заметили, что за все приходится платить, за свое самомнение — тоже. Как только Вы сталкиваетесь с проблемой, Вы, всегда опускаете руки?! У многого в Ржавчине глубоко-академический смысл, а колосальный инженерный опыт привел к таким (сложным для вашего понимания) решениям, и если ("с наскоку") у Вас возникли вопросы в понимании, то это не причина осуждать профессиональные решения, за них расплачивались кровью и бессоными ночами.
Я ожидал обоснованной критики языка, с точки зрения новичка, но здесь отражен только Ваш опыт (отстуствующего) навыка само-образования.
Да, язык — это и Ваш способ понимания, но этим его функции не ограничиваются, это еще и способ понимать проблемы, способность разграничения "разумного" и "не очень..." (сказать что решаемо или вероятно нет), а у Ржавчины еще и богатая (и доступная) история принятия решений.
Мне кажется, тут идёт вопрос разграничения между "клиентским" и "библиотечным" кодом. Названия условные. "Клиентский код" — условно тот, который решает вашу задачу. "Библиотечный" — набор базовых строительных кирпичиков. Так вот. Односвязный список, граф и т.п. для меня — как раз такие кирпичики. И при их реализации вы будете использовать техники, которые в нормальном коде не будете. К примеру unsafe. Который не является Exterminatus Incoming, а всего лишь возможностью обойти некоторые ограничения — если вы знаете что делаете. Вы пишете в основном на Java, поэтому просто для справки — в С++, к примеру, unsafe у вас по факту везде.
На Rust структуры данных пишутся так, чтобы работало быстро, а не со счётчиками ссылок на пустом месте. Там unsafe внутри и это нормально.
GUI фреймворк это как правило всё же дерево, причём готовое.
Не факт. Контрпример — назовите пример хорошего GUI фреймворка для Golang. Который на порядок проще.
А Rust — как системный. И Go, вследствие наличия GC, для GUI и граф-подобных структур, должен подходить сильно больше. Но поди ж ты.
Если что, я насчет Go вообще не в курсе — есть они там или нету.
Моё скромное мнение — серьёзный продакшен GUI подался в web или electron. Там уже не ролляет, на чём писать бизнес-логику. А старые крупные проекты просто не имеют желания шевелиться куда-то.
Окей, а каково ваше мнение на этот счет? Почему ни для Go ни для Rust до сих пор нет хорошего GUI-фреймвокра?
а не потому ли что Go/Rust значительно моложе всех достойных GUI-framework'ов, и что им нужно время?
Почему я отказался от Rust — тут описаны проблемы реализации GUI на Rust.
Там таки есть вполне валидные замечания. Для меня лично — переиспользование структур и поля в трэйтах, в качестве требований. Первую проблему можно было бы решить как в Go — alias член автоматом торчит из структуры всеми своими трейтами. Вторая ЕМНИП решена в Scala — но не в близких к системным языках. Могу конечно ошибаться.
Решение: Rc
+ RefCell
+ Weak
.
По моему опыту подход Elm гораздо надежнее. Но реализации его для Rust пока сыроватые.
Т.е. язык ещё бурно развивается и не для всех задач удобен.
Не вы первый об этом думали. Например: http://blog.paralleluniverse.co/2015/08/07/scoped-continuations/. Если коротко: монады не подходят, так как Раст — системный язык, который должен быть близок к железу, а железо у нас реализует не бета-редукции и эта-конверсии, а императивный поток выполнения.
Монада — это интерфейс, который по факту реализуют много типов, от Vec и Result и до Future, и отсутствие HKT не дает этим пользоваться. К системному программированию это не имеет ни какого отношения, монады уже есть и без всякой функциональщины, но только интерфейс с каждой из них отдельный.
Не всё так просто. Rust ведь тоже не дураки дизайнят, и там много людей которые тоже хотели бы видеть HKT в составе. Увы, на практике, до сих пор толком до конца никто не представляет как их красиво вписать и реализовать в Rust. Этот вопрос считается открытым и требующим глубокого исследования. Кратко со списком проблем можно ознакомиться в этом треде от withoutboats (core team).
Ждать пока этот исследовательский момент как-нибудь разрешится сообщество тоже не готово. Все хотят уже брать и писать крутые штуки на Rust. Без вменяемой асинхронности этого не получится. Соответственно, в данный момент для async/await не придумано лучших альтернатив в Rust. Это прагматичный выбор. Тем не менее он не отменяет монадок/HKT в будущем, если таки придумают как туда их красиво вписать.
Если же у Вас есть хорошие идеи/предложения по реализации HKT в Rust — добро пожаловать на официальный форум и в RFC-процесс. Выскажите свои идеи. Сообщество будет Вам очень благодарно, на самом деле.
Самая страшная беда — это NPE,
откуда такая информация?
утечки не такое уж частое явление
Но для их локализации требуется как минимум профилирование под нагрузкой. А в случае NPE в основном стектрейса достаточно. А для предотвращения NPE достаточно самых элементарных юнит тестов
Кажется, вы неправильно прочитали фразу.
Я не понял почему вы так решили.
В С++ любая проблема страшнее и решается через боль в разы большую, чем в языках с управляемыми средами. Не понятно, чем утечки памяти в С++ лучше, чем NPE.
«в языках с GC ничего страшнее NPE не возникнет» — вот только это не правда, и выше я объяснил почему.
Ещё разок — не понятно, на каком основании автор считает проблему NPE более важной чем проблему утечек памяти. Хотелось бы увидеть ссылку на соответствующие исследования например.
В языках с ГЦ утечка памяти в сейф коде означает только одно: ссылки достижимы из рута и являются живыми объектами. Почему они живые — уже десятый вопрос, но они были бы живыми при любом подходе к управлению памятью, даже в расте.
Статья видится из разряда услышал звон, но не понял где он. Хотя я и не знаю Rust, но этого и не нужно что бы понимать что язык создавался для системного программирования, и сравниваться его с Java, Kotlin или уже темболее Ruby абсурд, сферы применения различные.
Это мне напоминает схожие ставить где одни пишут как сделать web приложение на Go, а другие какой гемор Go. Хотя со вторыми я и согласен, но нужно учитывать под какие нужды создавался язык. А то что его пихают везде наивыне головы, а потом отхватывают другая проблема.
Вообще мне одно сравнение про плюсы понравилось, когда пишешь на них всегда остается ощущение, что ты идешь один по темной подворотне где-нибудь в гоповском районе.
Хотя Option почему-то является изменяемым
Нет, не Option является изменяемым, а метод take
вы можете вызвать только на изменяемом объекте. Потому что:
pub fn take(&mut self) -> Option<T> {
mem::replace(self, None)
}
take
заменяет значение на None
, что невозможно сделать на иммутабельном объекте.
Интересный подход просто, когда мутабильность определяется не в самом классе, а в момент создания экземпляра. В Java Optional — immutable. Небольшой разрыв шаблона.
Как вы думаете стоит ли переходить на Rust?
Почему только переходить? Можно же сочетать. Я, как питонщик, думаю об изучении раста в кач-ве низкоуровнего языка для решения определенного круга задач.
Если большинство языков пришли к тому, что надо отказаться от множественного наследования, то в Rust наследования нет вообще.
Большинство языков пришло к этому по той простой причине что множественное наследование в основном использовалось для реализации того что нам известно как интерфейсы, поэтому создание интерфейсов эту проблему решило.
Но наследование вообще (точнее, его отсутствие), увы, это слабое место Rust, и даже его автор этого не скрывает. Было много дискуссий на эту тему, и довольно немало людей жалуется что это одна из причин по которой на Rust очень сложно писать UI и другие подобные древовидные конструкции.
Если грубо, то получится что-то типа (псевдокод):
Window {
x
y
width
height
show()
hide()
}
Widget {
Window win
...
}
В Rust, если у нас есть объект типа Widget wg, к его окошку (для координат, к примеру) нам придется лезть весьма коряво — wg.win.show(), т.е. в любом случае нужно хорошо знать что у слоника внутри и пользоваться этим косвенно, вместо того чтобы в любом нужном объекте напрямую сделать что хочется, без тупого дублирования ссылок на методы в родительских классах.
Если у нас будет слой потолще, в духе Window > Widget > ListView > TreeListView, то можно будет просто умереть от лишней писанины, не говоря уже о том что в каждом классе придётся вручную прописать обращение к конструкторам (неважно что под ними подразумевается) и ещё кучу всего.
Да, само по себе это не является препятствием для написания UI и вообще чего угодно, в конце концов, всё это пишется на C где вообще нет классов, не говоря уже про наследование — но остальные свойства (требования) языка делают написание подобных вещей адским трудом, т.е. с точки зрения ООП это шаг назад, однако. Если рассматривать его как «лучший C» — да, может быть, но всё же полноценный ООП лучше, да есть и другие «лучшие C» без таких проблем.
В общем, Rust помогает решить ряд проблем для ленивых и нерадивых разработчиков за счёт бития по пальцам, но при этом создает массу трудностей для сравнительно простых (в других языках) вещей. Как всегда, безопасность и комфорт — вещи почти взаимоисключающие.
даже его автор этого не скрывает
Какой? Первый раз об этом слышу.
Но наследование вообще (точнее, его отсутствие), увы, это слабое место Rust
И почему же? Кто это говорит?
В Rust, если у нас есть объект типа Widget wg, к его окошку (для координат, к примеру) нам придется лезть весьма коряво — wg.win.show(), т.е. в любом случае нужно хорошо знать что у слоника внутри
Вообще-то решается проще:
trait Showable {
show()
}
struct Window {
x
y
width
height
}
impl Showable for Window {
show() { ... }
}
struct Widget {
Window win
...
}
impl Showable for Widget {
show() { self.win.show() }
}
И все. Снаружи пользователю не обязательно знать, что находится внутри Widget.
Все эти недопонимания возникают от попыток совместить и данные, и поведение в одном месте (классе). К счастью в Rust данные описываются отдельно от поведения, и это решает множество проблем. В комментах к статье Почему я отказался от Rust еще два года назад объяснили эти моменты.
И какого рода ошибку здесь можно допустить?
И вы не ответили на мой вопрос:
даже его автор этого не скрывает
Кто этот автор? Откуда этот вброс без фактов?
И какого рода ошибку здесь можно допустить?
Вы не в курсе какие ошибки можно допустить, дуплицируя код и ссылаясь на что-то много раз? Или что можно сослаться не туда в конце трудного рабочего дня?
Кто этот автор? Откуда этот вброс без фактов?
Вы не заметили ссылку в моём сообщении? Да, я её добавил через минуту, но просто потому что нажал «отправить» вместо «предосмотр». Почитайте её. Пусть он и не прямой автор языка, но всё же имеет достаточный вес чтобы быть к нему причастным.
А дискуссии на эту тему в сообществе Rust вы и сами нагуглите, по словам «rust object inheritance».
Честно говоря, мне несколько непонятно, почему такая агрессия на вполне справедливое замечание — что отсутствие нормально наследования является неудобным и увеличивает вероятность ошибок. Нравится вам всё делать ручками, повторяясь — делайте.
Проблема в том, что наследование реализации во многих популярных ООП-языках совмещено с образованием подтипа. И это несколько запутывает ситуацию. К каким проблемам это может приводить?
Например к тому, что вы пытаетесь просто переиспользовать код, но в итоге образуете подтип и кто-то, кто зависит уже от вашего кода, на это начинает рассчитывать. Но не вы.
Другая проблема — раздувание классов. В простейшем случае, у вас есть типы Rectangle
и Square
. И по логике Square
должен быть подтипом Rectangle
, но последний для хранения своего состояния требует больше памяти, чем первый (a
+ b
против просто a
). В итоге, применяя наследование для образование подтипа, вы чрезмерно раздувает Square
. И когда у вас глубокая иерерхия наследования, это делает ваши объекты просто монструозными.
В Rust было принято решение отказаться от наследования и образования подтипов вообще (системе типов в Rust подтипы не требуются). Если вам нужно переиспользовать какую-то структуру в другой, применяейте композицию. Это, конечно, заставляет писать шаблонный код в случае необходимости пробрасывать методы, но с введением делегатов ручной работы для этого станет сильно меньше.
А нужны ли эти делегаты в самом языке? Проблема прекрасно решается при помощи макросов.
К сожалению, при помощи макросов проблема не решается. Если только плагин к компилятору написать. Не решается потому, что макрос — это абстракция на уровне синтаксиса, а нам важна семантика. Например, вы не можете в макросе сделать такое:
#[derive_methods(Foo)]
struct MyStruct;
Потому что с точки зрения макроса, Foo
— это path, а не полноценная структура/типаж с объявленными методами, доступа к внутренностям Foo
макрос не имеет.
Вообще-то решается проще:
struct Widget { Window win ... } impl Showable for Widget { show() { self.win.show() } }
То есть методы всех базовых классов надо определить для всех наследников? А не всплывут ли там конструкции вида customTCB.tristateCB.cb.button.widget.show()?
Нет, скорее будет так: custom_tcb.show()
, внутри него self.tristate_cb.show()
, внутри него self.cb.show()
— и так далее.
Если так подумать, то это мало чем отличается от переопределения виртуальных методов в наследованных классах, часто же приходится уже руками еще и базовый вызывать, но это же никого не смущает, просто все привыкли.
Опять же, эту проблему можно решать процедурными макросами уже сейчас.
zharko_mi интересные первые впечатления, спасибо за статью.
Box — неизменяемое значение на куче
это ссылка, значение по ссылке менять можно
В первом примере если заменить
Box<Option<MyStruct>>
на
Opttion<Box<MyStruct>>
то можно будет писать None вместо Box::new(None)
А ещё можно делать так:
let value = 1;
let next = None;
let node = Node { value, next }; // привет любителям JS!
Удобно для конструкторов:
struct Point {
x: i32,
y: i32,
}
impl Point {
fn new(x: i32, y: i32) -> Self {
Point { x, y }
}
}
Пример со списком можно немного аккуратнее сделать (ссылка на плэйграунд):
fn add(&mut self, value: i32) {
let next = Link::new(value);
if let Some(ref last) = self.last {
last.borrow_mut().next = next.clone();
} else {
self.root = next.clone();
}
self.last = next;
}
Вообще люди из ML языков находят в расте много знакомого, кто раньше писал на скале — тоже.
Мне после JavaScript замыканий почему-то многие базовые концепты из раста очень легко дались.
Если вы посмотрите текущие вакансии, то в основном они в области блокчейна. Как я понимаю, они первыми адаптирует язык под свои нужды, по причинам необходимости быстрых крипто-вычислений, быстрых коммуникаций и наличия инвест-денег в этой нише.
Другие бизнес области представлены гораздо беднее и меньше, хотя я вижу rust как яп, который может использоваться во многих областях, когда под него появится больше "профильных библиотек" (через несколько лет, если все будет идти нормально).
На самом деле тут двойная ситуация: блокчейн хочет Rust, потому что Rust чрезвычайно надежен, что крайне важно, когда работаешь с деньгами. А позволить себе Rust разрабов (которые хотят много денег за свои знания) могут только жирные фирмы, готовые за сотрудника выложить 300+ в Мск.
Ну и на самом деле кроме блокчейна есть еще куча разных вкусных вакансий, но 70% таки да, блокчейн.
Первые шаги по Rust