Pull to refresh

Comments 74

как часто вы пишете изменяемые графовые структуры данных?
Не часто, но такое ограничение напрягает немного, особенно если язык претендует на общую применимость, а не узкую специализацию. А вы пишете на Rust? Сильно это мешает? Ведь чтобы перейти к следующему элементу, надо развернуть 3 обертки, т.е. сделать 3 обращения в случайное место памяти. Как там с производительностью? Не теряется ли преимущество низкоуровневости?
Визуально обёртки можно спрятать за кастомным типом, а раскрытие матрёшки в методе возвращающем Result (тогда большая часть проверок заменяется на "?") и/или макросе.

Изучая чего-нибудь…
Шаг первый: включить капитана очевидность
Шаг второй: использовать только свой "богатый" опыт
Шаг третий: встать в ступор от количества непонятного
Шаг четвертый: обозвать всех "идиотами"
Шаг пятый: озвучить Всем! свое мнение…
Шаг шестой: пойти искать, что-то еще более непонятное!


Вы верно заметили, что за все приходится платить, за свое самомнение — тоже. Как только Вы сталкиваетесь с проблемой, Вы, всегда опускаете руки?! У многого в Ржавчине глубоко-академический смысл, а колосальный инженерный опыт привел к таким (сложным для вашего понимания) решениям, и если ("с наскоку") у Вас возникли вопросы в понимании, то это не причина осуждать профессиональные решения, за них расплачивались кровью и бессоными ночами.
Я ожидал обоснованной критики языка, с точки зрения новичка, но здесь отражен только Ваш опыт (отстуствующего) навыка само-образования.
Да, язык — это и Ваш способ понимания, но этим его функции не ограничиваются, это еще и способ понимать проблемы, способность разграничения "разумного" и "не очень..." (сказать что решаемо или вероятно нет), а у Ржавчины еще и богатая (и доступная) история принятия решений.

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

Мне кажется, тут идёт вопрос разграничения между "клиентским" и "библиотечным" кодом. Названия условные. "Клиентский код" — условно тот, который решает вашу задачу. "Библиотечный" — набор базовых строительных кирпичиков. Так вот. Односвязный список, граф и т.п. для меня — как раз такие кирпичики. И при их реализации вы будете использовать техники, которые в нормальном коде не будете. К примеру unsafe. Который не является Exterminatus Incoming, а всего лишь возможностью обойти некоторые ограничения — если вы знаете что делаете. Вы пишете в основном на Java, поэтому просто для справки — в С++, к примеру, unsafe у вас по факту везде.

На Rust структуры данных пишутся так, чтобы работало быстро, а не со счётчиками ссылок на пустом месте. Там unsafe внутри и это нормально.

Практически любой GUI-фреймворк обычно выглядит как изменяемый граф виджетов.

GUI фреймворк это как правило всё же дерево, причём готовое.

Я к тому, что из-за этого фреймворков-то особо и нету до сих пор, слишком болезненно это делается.

Не факт. Контрпример — назовите пример хорошего GUI фреймворка для Golang. Который на порядок проще.

Не являюсь специалистом по Go, поэтому ничего не могу сказать конкретного про него. Рискну предположить, что Go позиционируется как язык для бэкэнда и там GUI просто «нинужен».

А Rust — как системный. И Go, вследствие наличия GC, для GUI и граф-подобных структур, должен подходить сильно больше. Но поди ж ты.

Окей, а каково ваше мнение на этот счет? Почему ни для Go ни для Rust до сих пор нет хорошего GUI-фреймвокра?

Если что, я насчет Go вообще не в курсе — есть они там или нету.

Моё скромное мнение — серьёзный продакшен GUI подался в web или electron. Там уже не ролляет, на чём писать бизнес-логику. А старые крупные проекты просто не имеют желания шевелиться куда-то.

Окей, а каково ваше мнение на этот счет? Почему ни для Go ни для Rust до сих пор нет хорошего GUI-фреймвокра?

а не потому ли что Go/Rust значительно моложе всех достойных GUI-framework'ов, и что им нужно время?
Плюсы для десктопа очень широко используются. Было бы хорошо их там заменить на Rust.
можно не ограничивать себя. я для GUI использую vue.js, который по rest api из go получает/записывает данные. намного удобнее и проще и меньше кода чем на c++ c qt например
Для веба и wasm есть похожий на реакт Yew для десктопа Conrod. В глубокой альфе да но есть еще Orbtk, Relm и Azul. Да, у них разработка в процессе но и язык очень молодой.

Там таки есть вполне валидные замечания. Для меня лично — переиспользование структур и поля в трэйтах, в качестве требований. Первую проблему можно было бы решить как в Go — alias член автоматом торчит из структуры всеми своими трейтами. Вторая ЕМНИП решена в Scala — но не в близких к системным языках. Могу конечно ошибаться.

И это плохо.
По моему опыту подход Elm гораздо надежнее. Но реализации его для Rust пока сыроватые.
RefCell — это не «изменяемая ссылка», а изменяемое значение, доступное по ссылке.
Rust это интересная попытка исправить си, но пока они не доделали async/await я точно туда не полезу, мне системное программирование не нужно, а вот что-то быстрое для тех задач куда сейчас любят пихать go может быть интересно.
Т.е. язык ещё бурно развивается и не для всех задач удобен.
Речь скорее всего про то, что оно должно появиться в stable. А потом должны на нем переписать tokio, а потом actix, а потом…
Лучше бы они сделали HKT, монады и for comprehension. async/await — громозкий, неудобный и негибкий костыль.

Не вы первый об этом думали. Например: http://blog.paralleluniverse.co/2015/08/07/scoped-continuations/. Если коротко: монады не подходят, так как Раст — системный язык, который должен быть близок к железу, а железо у нас реализует не бета-редукции и эта-конверсии, а императивный поток выполнения.

А извращение async/await для системного языка подходит?
Монада — это интерфейс, который по факту реализуют много типов, от Vec и Result и до Future, и отсутствие HKT не дает этим пользоваться. К системному программированию это не имеет ни какого отношения, монады уже есть и без всякой функциональщины, но только интерфейс с каждой из них отдельный.

Не всё так просто. Rust ведь тоже не дураки дизайнят, и там много людей которые тоже хотели бы видеть HKT в составе. Увы, на практике, до сих пор толком до конца никто не представляет как их красиво вписать и реализовать в Rust. Этот вопрос считается открытым и требующим глубокого исследования. Кратко со списком проблем можно ознакомиться в этом треде от withoutboats (core team).


Ждать пока этот исследовательский момент как-нибудь разрешится сообщество тоже не готово. Все хотят уже брать и писать крутые штуки на Rust. Без вменяемой асинхронности этого не получится. Соответственно, в данный момент для async/await не придумано лучших альтернатив в Rust. Это прагматичный выбор. Тем не менее он не отменяет монадок/HKT в будущем, если таки придумают как туда их красиво вписать.


Если же у Вас есть хорошие идеи/предложения по реализации HKT в Rust — добро пожаловать на официальный форум и в RFC-процесс. Выскажите свои идеи. Сообщество будет Вам очень благодарно, на самом деле.

И, кстати, в системном языке C++ есть HKT, и широко применяется в библиотеках.
Так зачем повторять хаскел?
Потому что это удобно. Scala, хоть и вполне поддерживает императивный стиль, и даже макросы async/await, все расно «повторияет хаскел». И мой опыт говорит, что единственный способ разобраться с async/await-кодом — переписать его на for.
Самая страшная беда — это NPE,

откуда такая информация?
утечки не такое уж частое явление

Но для их локализации требуется как минимум профилирование под нагрузкой. А в случае NPE в основном стектрейса достаточно. А для предотвращения NPE достаточно самых элементарных юнит тестов

Кажется, вы неправильно прочитали фразу. Если в Си или С++ при неаккуратном обращении с указателями можно незаметно поломать в программе вообще всё, то в языках с GC ничего страшнее NPE не возникнет.
Кажется, вы неправильно прочитали фразу.

Я не понял почему вы так решили.

В С++ любая проблема страшнее и решается через боль в разы большую, чем в языках с управляемыми средами. Не понятно, чем утечки памяти в С++ лучше, чем NPE.

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

Ещё разок — не понятно, на каком основании автор считает проблему NPE более важной чем проблему утечек памяти. Хотелось бы увидеть ссылку на соответствующие исследования например.
В Языках с GC вообще еще и утечки памяти бывают. Вот сталкивался с тем что WCF под Mono тёк. Вот видел Java сервис который надо было иногда пере запускать потому что заканчивалась память на сервере. Так что такое себе. Не NPE единым.
Память течет вообще везде. Если в C++ выделять память и никогда не удалять, магические гномики от оома не спасут.

В языках с ГЦ утечка памяти в сейф коде означает только одно: ссылки достижимы из рута и являются живыми объектами. Почему они живые — уже десятый вопрос, но они были бы живыми при любом подходе к управлению памятью, даже в расте.

Статья видится из разряда услышал звон, но не понял где он. Хотя я и не знаю Rust, но этого и не нужно что бы понимать что язык создавался для системного программирования, и сравниваться его с Java, Kotlin или уже темболее Ruby абсурд, сферы применения различные.
Это мне напоминает схожие ставить где одни пишут как сделать web приложение на Go, а другие какой гемор Go. Хотя со вторыми я и согласен, но нужно учитывать под какие нужды создавался язык. А то что его пихают везде наивыне головы, а потом отхватывают другая проблема.

Rust ставит достаточно высокую планку для того, кто хочет начать писать осмысленный код. Начать с hello world и перейти сразу скажем к написанию web-сервиса, или обертки для C библиотеки, или игры типа тетриса — не получится. Надо разбираться с владением, заимствованием, системой видимости пакетов, своим взглядом на наследование, обработку ошибок. Ну т.е. если с тем же go можно взять примеры и начать их делать, то с rust лучше будет сначала прочитать книжку, а потом начинать делать примеры. По крайней мере это мой опыт.
не так уж и много, сравнивая например с пластом минимального и достаточного знания с++ для решения практических задач
Ну в достаточной степени знать C++ как мне кажется вообще не возможно. А минимально знаний, для того чтобы накалякать что-то типа консольного тетриса можно набраться за неделю. Да у вас будет течь память, да вы скорее всего не правильно будете ловить исключения, да скорее всего будете забивать на const, но сможете что-то скомпилировать и оно будет даже работать как-то.
вам кажется. Знать на 100% не получится — голова заболит, а вот всё необходимодостаточное вполне можно выучить и за год в среде с культурой написания кода.
Ну как скажете. Я на него потратил суммарно лет 5 разрабатывая на нем профессионально; при этом начал что-то на нем писать году примерно в 98-ом. Я не считал до сих пор, что знаю его достаточно хорошо; но если вы настаиваете — то спорить не буду.

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

это уже смотря как учить и что считать достаточным. Я за свои 5 лет писал на плюсах с 03 по 17-е и в достаточности своих познаний уверен. Представляете, знать весь стандарт назубок для написания корректных программ необязательно!
Можно сразу перейти к написанию осмысленного кода, если не прыгать сразу в гущу проблем. Это как в Java после хелло ворлда пойти пытаться сразу пул соединений писать с автоматическим возвратом в этот самый пул и детерминированной гарантированной очисткой ресурсов. Никто так не делает. Просто все интуитивно знают, что «связный список» — классическая задача на языках с ГЦ, и идут автоматически пытаться её реализовать в раст. Это неправильный подход.
Хотя Option почему-то является изменяемым

Нет, не Option является изменяемым, а метод take вы можете вызвать только на изменяемом объекте. Потому что:


pub fn take(&mut self) -> Option<T> {
    mem::replace(self, None)
}

take заменяет значение на None, что невозможно сделать на иммутабельном объекте.

Интересный подход просто, когда мутабильность определяется не в самом классе, а в момент создания экземпляра. В Java Optional — immutable. Небольшой разрыв шаблона.

Даже не в момент создания экземпляра, а в момент привязки к переменной. Если написать "let v1 = Vec::new(); let mut v2 = v1;", то v2 будет мутабельный вектор.

Как вы думаете стоит ли переходить на Rust?

Почему только переходить? Можно же сочетать. Я, как питонщик, думаю об изучении раста в кач-ве низкоуровнего языка для решения определенного круга задач.
UFO just landed and posted this here
Утечки в C++ (как и в почти любом другом языке) решаются с помощью GC. Правда, все подключенные либы тоже должны его использовать, разумеется.
Если большинство языков пришли к тому, что надо отказаться от множественного наследования, то в 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 еще два года назад объяснили эти моменты.

Какой? Первый раз об этом слышу.

Этот.

show() { self.win.show() }

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

И какого рода ошибку здесь можно допустить?


И вы не ответили на мой вопрос:


даже его автор этого не скрывает

Кто этот автор? Откуда этот вброс без фактов?

И какого рода ошибку здесь можно допустить?

Вы не в курсе какие ошибки можно допустить, дуплицируя код и ссылаясь на что-то много раз? Или что можно сослаться не туда в конце трудного рабочего дня?
Кто этот автор? Откуда этот вброс без фактов?

Вы не заметили ссылку в моём сообщении? Да, я её добавил через минуту, но просто потому что нажал «отправить» вместо «предосмотр». Почитайте её. Пусть он и не прямой автор языка, но всё же имеет достаточный вес чтобы быть к нему причастным.

А дискуссии на эту тему в сообществе Rust вы и сами нагуглите, по словам «rust object inheritance».

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

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


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


Другая проблема — раздувание классов. В простейшем случае, у вас есть типы Rectangle и Square. И по логике Square должен быть подтипом Rectangle, но последний для хранения своего состояния требует больше памяти, чем первый (a + b против просто a). В итоге, применяя наследование для образование подтипа, вы чрезмерно раздувает Square. И когда у вас глубокая иерерхия наследования, это делает ваши объекты просто монструозными.


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

А нужны ли эти делегаты в самом языке? Проблема прекрасно решается при помощи макросов.

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


#[derive_methods(Foo)]
struct MyStruct;

Потому что с точки зрения макроса, Foo — это path, а не полноценная структура/типаж с объявленными методами, доступа к внутренностям Foo макрос не имеет.

UFO just landed and posted this here
Вообще-то решается проще:
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 замыканий почему-то многие базовые концепты из раста очень легко дались.

Я на Java тоже много лет, несколько больше чем вы, но тоже активно изучаю Rust в свободное время. Надеюсь он займет свою нишу для написания быстрых микро-макро сервисов, в том числе и для Enteprise ниши, которую занимает java/.net/scala, а не только blockhchain. Много чего для таких задач не хватает с точки зрения библиотек. Мне он также интересен для embedded. Спасибо.
На данный момент основная ниша Rust — это блокчейн? Не знал этого. Почему так считаете?

Если вы посмотрите текущие вакансии, то в основном они в области блокчейна. Как я понимаю, они первыми адаптирует язык под свои нужды, по причинам необходимости быстрых крипто-вычислений, быстрых коммуникаций и наличия инвест-денег в этой нише.
Другие бизнес области представлены гораздо беднее и меньше, хотя я вижу rust как яп, который может использоваться во многих областях, когда под него появится больше "профильных библиотек" (через несколько лет, если все будет идти нормально).

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


Ну и на самом деле кроме блокчейна есть еще куча разных вкусных вакансий, но 70% таки да, блокчейн.

UFO just landed and posted this here
Sign up to leave a comment.

Articles