Здравствуйте, alex_public, Вы писали:
_>Что-то ты совсем заговорился. ))) Ты предложил рассмотреть в виде этой "особой ситуации" случай вылета приложения по исключению и при этом считаешь это неочевидным поведением, которое будет сложно отследить? )))
Нет, я не хочу вникать в нюансы, вот типа сегодня это main, а завтра -- функция внутри системы. Сегодня это исключение, а завтра -- ранний выход из функции по редкому условию (ну там, решили ошибку вернуть).
В этом есть особенность Rust -- без unsafe он позволяет строго подмножество "безопасных" программ. Эта программа может быть безопасной в том виде, в котором она написана тобой, но это можно показать только внимательным анализом: например, что это main, который по исключению просто остановит программу. Что выход их функции может быть только по исключению, а не по какому-то условию, который кто-нибудь через полгода добавит в этот код.
_>Эээ что? Откуда это ты изобрёл какую-то связь между Arc и иммутабельностью данных?
Потому что ты сможешь получить изменяемую ссылку на внутренности Arc только в одном случае -- если это единственный экземпляр Arc. Иначе придётся использовать то, что в Rust называется "внутренняя изменяемость". Mutex -- один из примеров внутренней изменяемости (все они сводятся внутри к UnsafeCell, который в Rust является единственным не-UB вариантом иметь разделяемую изменяемую ссылку).
_>И так, у нас есть конкретный, работающий пример на C++. ОК, Rust не позволяет переписать его дословно, потому что ему не нравится использование локальной переменной в других потоках. Ну ОК, сделай её не локальной переменной а выделенной в куче (Arc) и всё — в чём проблема то? Вся эта борьба с BC вообще не имеет никакого отношения к тому нюансу, который я хотел продемонстрировать этим примером. Т.е. да, Rust должен был "слиться", но намного позже и совсем в другом смысле. Я не ожидал, что у всех участников дискуссии возникнет проблема даже с банальным повтором такого простейшего кода.
Потому что Rust требует отсутствие гонок данных. Можно умозрительно показать, что компилятор может это статически доказать (используя borrow checker) только если все твои локальные и глобальные данные покрыты одним мьютексом. Иначе потенциально возможна ситуация, когда одни данные могут изменятся конкурентно под двумя разными мьютексами.
Тут два варианта в Rust:
1. Ты знаешь, что делаешь и ты можешь спрятать свою структуру данных за безопасным интерфейсом.
2. Ты думаешь, что знаешь, что делаешь, но на самом деле нет. Тогда тебе лучше переформулировать свой код в стиле Rust.
Поэтому нет смысла такие маленькие примеры рассматривать -- Rust требует другого подхода к проектированию систем. С одной стороны, проще писать "локальный" код -- можно не думаю, добавит ли этот ранний выход проблем с воооон теми потоками или нет ("fearless"), но сложнее проектировать систему "глобально" -- требуется хорошее понимание Rust. Но многие задачи укладываются в уже существующие парадигмы в Rust -- бери библиотеку, да используй.
Да, Rust примет строгое подмножество корректных программ (т.е есть корректные программы, которые Rust без unsafe не примет) -- иначе не нужен был бы unsafe. Но те программы, которые он примет будут иметь отсутствие определённых эффектов (data races, etc).
Здравствуйте, alex_public, Вы писали:
_>Он конечно возможно и некорректный, но исключительно в административном смысле. Потому как компилятор переварит его без единого возражения. Более того, он может скомпоновать этот код в красивую библиотечку, которую будут распространять без исходников (хотя на самом деле сейчас большинство народа не смотрит исходники даже если они открыты). Понятна моя мысль? )
Разумеется. Я не вижу смысла доводить это до какого-то абсолюта -- разработка в целом подразумевает какой-то уровень доверия. Цель не построить железобетонную защиту, а в большой степени освободить себя от определённого класса ошибок.
_>А как ты поймёшь то, где у тебя unsafe код, а где нет? Вот я сделал вызов некой функции hack_rust (см. код в сообщениях выше) — это безопасно или нет? Как понять? А например использование std::mem::forget — это точно всегда безопасно? )))
unsafe код должен быть (да, административными методами) ограничен маленькой областью с безопасным интерфейсом.
std::mem::forget должен быть всегда безопасен (в смысле гарантий Rust), но были ошибки, которые делали его небезопасным для определённых структур данным. Ну и в целом, утечки памяти -- это "небезопасно", но уже с точки зрения бизнес требований.
_>На самом деле для решения этой проблемы надо просто следовать определённым не сложным правилам. В любом языке.
Нет, это не подтверждается историей разработки software, и, в частности CVE. Уже давно пора прекратить этот миф.
Здравствуйте, alex_public, Вы писали:
_>·>Можешь показать хоть один более менее крупный проект C++ с безопасным кодом? Для rust такое есть. А пока получается как "на С++ можно писать безопасно, но мы не хотим", прям классика "я всегда могу бросить". _>Я думаю что очень многие C++ проекты начатые после 2014 используют безопасные подходы. Т.е. технически это можно было делать и намного раньше, но это было не формализировано, не стандартизировано и т.п. А с приходом C++14 это уже просто норма языка, которую не соблюдают разве что следуя традициям проекта начатого давным давно. Насчёт крупности даже не знаю — мне как-то лень искать какие крупные C++ проекты появились в последнее время. В моих проектах как раз всё безопасно (ну не считая взаимодействия с C библиотеками, но это вынужденное зло), но ты их точно не найдёшь в открытом виде, так что вряд ли может служить аргументом.
Т.е. существует где-то в ненаблюдаемой реальности. Сложно с таким спорить.
_>·>Ты просто всё свалил в одну кучу. Вот и получилась чушь. Сборщики мусора не решают "[все] проблемы управления памятью", а вполне определённые, например, обращение к освобождённой или неинициализированной памяти, висящие объекты. Но не решают проблемы утечек памяти, например. Так и Rust не решает "[все] проблемы многопоточности", а позволяет защититься от вполне определённых проблем, например, data race. C++ не позволяет. Нужно ли это? Конечно, ибо data race довольно частая и труднообнаружимая проблема, и её решение значительно упрощает написание кода. Ровно так же и сборщики мусора — решают наиболее частую и вредную проблему. _>Хы, ты совсем не понял аналогию. Смотри как выглядит в реальности. Можно чётко выделить 3 группы подходов в управление:
_>1. Полностью автоматическое и не требующее мозга для использование. Характеризуется пониженной производительностью и снижением области применения. _>В управление памятью примером этого является сборщик мусора (например как в Java или ).
OutOfMemory можно в java получить? Можно. unsafe есть? Есть. native есть? Есть. Значит мозг таки требуется.
_>В управление многопоточностью примером этого является модель акторов (например как в Erlang).
Дедлок возможен? Возможен. NIF есть? Есть. ETS есть? Есть. Значит мозг таки требуется.
Значит по-твоему "никаких гарантий".
Или давай раскрывай что конкретно гарантирует "Полностью автоматическое управление".
_>2. Автоматическое управление (всё чётко проверяется компилятором), но требующее приложение мозга (программист должен указывать компилятору свойства объектов). Имеет нулевой оверхед и подходит для любой области. _>В управление памятью примером этого являются "умные указатели" (как в Rust или C++).
А &-ссылки в C++ тоже запрещать надо для этого или как?
Как вообще можно по куску кода понять — используется ли в данном коде "правильное управление памятью" или не очень?
_>В управление многопоточностью подобного решения пока к сожалению не существует!
Суть в том, что rust _компилятор_ проверяет код и чётко гарантирует отсутствие data race. Иными словами, даёт safety guarantees от data race. Если проверку отключить явно (т.е. использовать unsafe), то понятное дело, не проверяет, и гарантии не даёт. КО.
C++ же гарантировать отсутсвие data race никак не может в принципе.
Иными словами. Тезис в том, что для данного куска кода:
1. Если в этом коде нет unsafe
2. Если все используемые в этом коде зависимости safe.
3. Если rust компилятор выдал "ок".
=> то код обладает safety guarantees для данного куска кода.
Подобное недоступно в C++ никакими техническими средствами, только если вручную внимательно просмотреть код глазками и убедиться в отсутствии всех "неправильных" конструкций. Например, вот код, очень похожий на твой:
Как узнать что тут есть ошибка?
В rust такое тупо не скомпилится.
В этом и отличие.
_>·>По поводу использования unsafe vs "правильный С++" — это опять словоблудие. Вот пришел тебе пулл реквест на review — в случае rust ты можешь сразу увидеть unsafe и насторожиться. Можно даже тулзы соответсвтвующим настроить, чтобы коммиты с unsafe реджектить или назначать более серьёзный review всей командой. В случае С++ любое изменение потенциально может поломать всё что угодно и нет никакого простого и однозначного способа отличить безопасные изменения от опасных. _>Вот в том то и дело, что не сможешь увидеть. Потому что в Rust применение usafe блока не обязывает помечать функцию как unsafe. Так что по факту ты можешь просто вызвать в своём коде некую вполне себе прилично выгляющую safe функцию из какой-то посторонней библиотеки и тем самым неявно получить в коде своего приложения дикий unsafe код. Который на ревью никто не увидит.
Ровно то же и в java — вызываешь приличную функцию, а она дёргает native и всё крошит. В чём отличие-то?
_>Более того, в стандартной библиотеке языка мы можем увидеть такие милые функции как например std::mem:forget, которые позволяют натворить много чего весёлого. Да, и если что, forget тоже не помечена как unsafe функция...
forget is not marked as unsafe, because Rust's safety guarantees do not include a guarantee that destructors will always run. For example, a program can create a reference cycle using Rc, or call process::exit to exit without running destructors. Thus, allowing mem::forget from safe code does not fundamentally change Rust's safety guarantees.
_>Так что если реально хотим сидеть в безопасной зоне (хотя бы по памяти — про многопоточность даже заикаться не надо!), то на Rust тоже будет небольшой талмудик требований, а не одна строчка "без unsafe".
А что же тогда будет гарантией "безопасной многопоточности"? Что вообще значит "безопасная многопоточность" по-твоему? В каких системах в принципе есть безопасность?
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, FR, Вы писали:
FR>Скажем для rust даже сейчас с появлением процедурных макросов это все еще справедливо. FR>Вообще жаль что развитие Nemerle заглохло, если бы пошли по пути Kotlin (андроид и native) все могло бы быть и по другому.
Nemerle — типичный результат "сумрачный русских гениев": все кривое говно, поэтому надо фигачить с нуля всемогутый фреймворк! Конец был немного предсказуем.
Здравствуйте, WolfHound, Вы писали:
WH>>>То есть без unsafe не можешь. _>>Ох, вот всё тебе надо разжёвывать. Показываю на пальцах: имеешь в дальнем углу какой-то из используемых тобой библиотек например такую функцию: WH>То есть без unsafe не можешь.
Ну вот, снова очередной явный слив. Тебе прямо показали компилируемый код, но ты попробовал сделать вид, что не заметил его. Уже во второй раз в данной дискуссии.
_>>Это если ты можешь точно создать условия, при которых возникает дедлок. А он может возникать только в специфических условиях, когда допустим какой-то поток не успевает отработать или наоборот слишком быстро завершается. WH>Делаем дамп зависшего приложения и смотрим. WH>Тоже мне проблема.
Что такое "дамп зависшего приложения" и в каких условиях твои приложения выдают эту сущность пользователю?
WH>>>Ещё раз. Данный код корректен. Нужно просто доработать библиотеку. _>>Угу, точно так же как и на C или Ассемблере. ))) WH>Нет. На С у тебя программа расстреливает память и разлетается на куски. WH>А тут приложение зависает, не портя память. WH>Более того эту конкретную проблему можно починить сделав планировщик более умным.
Это всё мелкие детали. Ключевой момент в том, что компилятор свободно позволяет создавать некорректные приложения, и исправляются ошибки как и обычно руками программиста. Т.е. в этом нет ничего плохого или необычного — всё как всегда и как и во всех языках. Ненормальна тут только твоя вера в некую магию одного языка.
WH>>>Мьютексы они разные бывают. Вполне возможна реализация, работающая без обращения к ядру ОС. WH>>>В данном случае я про их реализацию вообще ничего не говорил. WH>>>Только про модель использования. _>>И про модель тоже не верно было. ))) WH>Что не верно? Под мьютексом у нас живёт разделяемый ресурс. WH>Если его захватывать надолго, то все потоки будут отвисать.
Хы, вот в том то и дело, что в реальности мьютексы чаще используют не для защиты конкретного ресурса, а для различных синхронизаций. Т.к. это один из самых базовых и низкоуровневых примитивов синхронизации. Например conditional variable работают через мьютексы (и кстати Rust тут не исключение https://doc.rust-lang.org/std/sync/struct.Condvar.html) и ещё много чего.
_>>Кстати, расскажи ка какие ещё реализации мьютексов по твоему применяются для синхронизации системных потоков. WH>Спинлоки. Если ты захватываешь мьютекс на небольшое время, то они отлично работают.
Xa, xa. ))) Вообще то спинлоки давным давно встроены в реализации тех самых системных мьютексов, на большинстве современных ОС. Это сделано для оптимизации — при попытке захвата мьютекса поток в начале некоторое краткое время работает по схеме спинлока и только потом идёт вызов кода ядра, блокирующий поток. Так что говорить что спинлок — это альтернатива системным мьютексами весьма забавно. )))
_>>Ты же вроде предпочитал говорить исключительно о них. Или же мы всё же включим в обсуждение и всяческие реализации лёгких потоков? ))) WH>Откуда ты это взял?
Ну так, когда я заговорил о гонках данных в рамках одного системного потока (что легко происходит при работе с различными зелёными потоками, сопрограмми и т.п.), то ты попытался резко уйти от этой темы, сказав что я слишком широко трактую понятие гонок (хотя на самом деле это стандартная трактовка). Возможно это было потому, что Rust ничего не предлагает в данной области? )))
_>>Ну так ты же в предыдущем сообщение написал что: "1)Это можно делать на любых языках с метарограммированием. Динамическая типизация тут не нужна. Да и руками написать такое не сложно.". Вот меня и интересует как ты это сделаешь с помощью метапрограммирования на не динамическом языке, например на Rust'е. WH>Генерируем обёртку и работаем через неё. Можно даже сделать интерфейс, который делает объекты неразличимыми.
Так это изменит тип объекта, сломав всю дальнейшую работу системы.
WH>А теперь ты отвечай, зачем нужен такой идиотизм.
Эм, у тебя короткая память? ))) Вообще то данный момент возник не из неоткуда, а из вполне конкретного обсуждения реализации гарантированного контроля доступа (например на предмет проверки захвачен мьютекс или нет) к переменной в динамических языках. В общем то очевидная вещь, просто ты неожиданно сказал что и без динамической типизации легко это повторишь...
_>>Ты серьёзно не можешь придумать задачу, в которой поток будет использовать целых две переменных, с разным временем жизни? Скорее на практике трудно встретить задачу, в которой не будет такой ситуации. Потому как в реальности данные обычно инициируются не в одном месте, а где-то по ходу исполнения приложения. WH>Серьёзно. Я такое ни разу не видел. Ни разу про такое не слышал. И не слышал ни одной жалобы на дизайн мьютексов в rust. Видимо ты один кто так мьютексы использует.
Так жалоб то и быть не может, потому как реализация мьютексов в Rust'е включает в себя и стандартную. Т.е. достаточно создать в Rust мьютекс вокруг пустых данных и получишь в руки тот самый инструмент, которым пользуются во всех остальных языка, операционных системах и т.п.
Жалобы могут быть разве что на вопли компилятора, который увидит доступ к разделяемым данным, но не сможет увидеть, что этот доступ безопасен. Ну это снимается через unsafe...
WH>И раз у тебя такое происходит постоянно, то ты должен был легко показать реальный пример. WH>Но ты всегда уходишь от ответа на вопрос зачем. Ибо ты не знаешь. Просто придумываешь говнокод ради спора. WH>Или может, понимаешь, что нагородил говнокод, и коллеги засмеют?
На самом деле именно мьютексы у меня очень редко встречаются для целей защиты данных. Потому как я обычно или придерживаюсь модели акторов в архитектуре приложения или же реализую какой-то небольшой участок сверхбыстродействующего кода через разделяемую память, синхронизируемую с помощью CAS.
Но вот ситуация с тем, что потокам могут понадобиться данные с разным временем жизни — это абсолютно нормальное и повседневное явление.
_>>Хы. Вообще то считай что весь мир целиком живёт по этому самому сценарию. Потому как во всех языках, библиотека, OS API и т.п. мьютексы работают именно на область кода, а не на область данных. WH>А по факту их всегда используют на область данных. WH>Исключений я не видел.
Ну да, т.е. вся индустрия идиоты и проектируют свои интерфейсы для одного, а используют их совсем для другого. Конечно, конечно. ))) Кстати, помнится я уже слышал от тебя аналогичные утверждения, по какому-то совсем другому поводу.
Здравствуйте, alex_public, Вы писали:
_>Ну вот, снова очередной явный слив. Тебе прямо показали компилируемый код, но ты попробовал сделать вид, что не заметил его. Уже во второй раз в данной дискуссии.
Я увидел там unsafe без создания safe интерфейса.
Нормальные люди так не делают.
_>Что такое "дамп зависшего приложения" и в каких условиях твои приложения выдают эту сущность пользователю?
Зависит от того что за приложение.
_>Это всё мелкие детали. Ключевой момент в том, что компилятор свободно позволяет создавать некорректные приложения, и исправляются ошибки как и обычно руками программиста. Т.е. в этом нет ничего плохого или необычного — всё как всегда и как и во всех языках. Ненормальна тут только твоя вера в некую магию одного языка.
У меня есть чёткое понимание того какие классы ошибок устраняются и как именно это происходит.
_>Хы, вот в том то и дело, что в реальности мьютексы чаще используют не для защиты конкретного ресурса, а для различных синхронизаций. Т.к. это один из самых базовых и низкоуровневых примитивов синхронизации. Например conditional variable работают через мьютексы (и кстати Rust тут не исключение https://doc.rust-lang.org/std/sync/struct.Condvar.html) и ещё много чего.
Причём тут conditional variable?
Они используются для того чтобы сообщить что данные которые защищает мьютекс изменились.
Обрати внимание на сигнатуры. Метод wait поглощает MutexGuard для того чтобы освободить его. И возвращает новый MutexGuard после того как он снова захватил ресурс.
В других языках происходит то же самое. Просто они корректность работы с мьютексами не контролируют.
_>Xa, xa. ))) Вообще то спинлоки давным давно встроены в реализации тех самых системных мьютексов, на большинстве современных ОС. Это сделано для оптимизации — при попытке захвата мьютекса поток в начале некоторое краткое время работает по схеме спинлока и только потом идёт вызов кода ядра, блокирующий поток. Так что говорить что спинлок — это альтернатива системным мьютексами весьма забавно. )))
А почему такая оптимизация работает? Да по тому, что нормальные люди захватывают мьютекс на несколько десятков инструкций.
Ну и ты тут начал говорить про реализации, которые сразу в ядро лезут.
_>Ну так, когда я заговорил о гонках данных в рамках одного системного потока (что легко происходит при работе с различными зелёными потоками, сопрограмми и т.п.), то ты попытался резко уйти от этой темы, сказав что я слишком широко трактую понятие гонок (хотя на самом деле это стандартная трактовка). Возможно это было потому, что Rust ничего не предлагает в данной области? )))
Тебя носит из стороны в сторону. Чёрт тебя разберет, что ты там имел в виду.
Гонками можно назвать и порядок прихода сообщений в очередь. При таком определении гонки и в эрланге есть.
А если мы тупо делаем зелёные потоки и свой планировщик, то для rust'а это ничем не будет отличаться от системных потоков.
Будут работать всё те же механизмы.
А то, что тебе нужно это объяснять много говорит о том насколько хорошо ты понимаешь rust.
WH>>Генерируем обёртку и работаем через неё. Можно даже сделать интерфейс, который делает объекты неразличимыми. _>Так это изменит тип объекта, сломав всю дальнейшую работу системы.
Читай выделенное.
WH>>А теперь ты отвечай, зачем нужен такой идиотизм. _>Эм, у тебя короткая память? ))) Вообще то данный момент возник не из неоткуда, а из вполне конкретного обсуждения реализации гарантированного контроля доступа (например на предмет проверки захвачен мьютекс или нет) к переменной в динамических языках. В общем то очевидная вещь, просто ты неожиданно сказал что и без динамической типизации легко это повторишь...
На практике такого вообще не бывает.
Если ты создаёшь тип, объекты которого нужно разделять между потоками, то объекты этого типа без защиты вообще не нужны.
Короче, с твоей стороны очередной высосанный из пальца говнокод.
_>Но вот ситуация с тем, что потокам могут понадобиться данные с разным временем жизни — это абсолютно нормальное и повседневное явление.
Раз это явление повседневное то ты должен легко дать ответ на этот вопрос.
Но ты уже несколько сообщений вертишься.
Почему?
Может по тому, что ты ради спора насосал говнокод из пальца, а реальных примеров в твоей практике никогда не было?
_>Ну да, т.е. вся индустрия идиоты и проектируют свои интерфейсы для одного, а используют их совсем для другого. Конечно, конечно. )))
У индустрии не было borrow checker'а. А изначально и шаблонов не было. Так что она тупо не могла сделать нормальный интерфейс мьютекса.
А как только появился язык с BC и шаблонами так сразу мьютекс начал защищать данные.
И никто против этого слова не сказал. Наоборот, все этому очень радуются.
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, WolfHound, Вы писали:
_>>Ну так инструментом то всё же надо уметь пользоваться... Т.е. реализация многопоточности через акторы конечно же является мощными защитным инструментом, но не настолько, чтобы можно было посадить обезьяну и получать гарантированный код от её случайных нажатий. Хотя бы минимальное понимание как применять инструмент всё же требуется. ))) WH>А как же гарантированное математикой отсутствие проблем?
Ещё раз: отсутствие проблем гарантируется при соблюдение некоторых условий. И смысл этой "математики" тут в том, что мы заменяем сложные и неочевидные условия, на достаточно простые. Правда при этом слегка сужая область применения.
_>>Грубо говоря, если бы стандарт C++14 был принят в 90-ые годы, то большинство C++ библиотек имело бы унифицированный безопасный интерфейс, а такие языки как D, Nim, Rust вообще не появились бы на свет за ненадобностью. Однако исторически сложилась другая картина и сейчас очень интересный момент для этих новых языков — они зародились для замены старого стандарта C++, но пока из разрабатывали появился новый стандарт C++, при котором они не особо то и нужны. Но какую-то небольшую аудиторию они занять смогли, да и ситуация с библиотеками C++ будет выправляться очень долго. Так что ситуация неоднозначная — посмотрим куда в итоге развернётся индустрия. WH>В стандарте С++ появился borrow checker?
BC в том виде, в каком он сейчас в Rust'е, не нужен в C++, т.к. он позволяет реализовать только некое подмножество корректных программ (многие случаи абсолютно корректного кода всё равно не пропускает), что противоречит концепциям C++.
Ну а некое подмножество BC, позволяющее безопасно управлять памятью появилось в стандарте языка C++11 и было доведено до ума в C++14.
_>>Лично под мой вкус подходят оба языка. Изначально я думал, что точно со временем надо будет переходить на Rust, только подождать пока там более менее устаканятся инструменты и библиотеки (с этим кстати до сих пор проблема). Ну а пока ждал, уже в современном C++ начали появляться новинки, обходящие Rust. WH>Это какие?
Например сопрограммы (на уровне языка, со всеми вытекающими из этого вкусняшками), контракты (которые на уровне языка — компилятор может использовать для оптимизации кода), вычисления на этапе компиляции (полноценный код с контейнерами, полиморфизмом и т.п.), транзакционная память и т.д.
_>>На самом деле я наверняка знаю про плюсы Rust'а побольше тебя. Там довольно много серьёзных вкусняшек, в том числе и в сравнение с C++. Нюанс в том, что ты в данной дискусии упираешь именно на ту область, в которой у Rust в реальности плюсов нет — в реализации многопоточного кода. WH>А ничего что его создавали для решения этой задачи? WH>Более того успешно написали на rust код который не смогли на С++ из-за того что программа разлеталась на куски.
Ну во-первых переписали пока не продукт, а его маленький кусочек. И думаю что если бы этот кусочек переписали бы на современный C++, то результат был бы в точности такой же.
_>>Вообще то уже был явно продемонстрирован один из примеров. Но он естественно не единственный. WH>От жеж лицемер. В С++ в том примере будет тоже самое.
Тебе уже наглядно продемонстрировали, что не будет. Тот пример кода на C++, что ты так не смог повторить, не требует никаких контейнеров. А его точный аналог на Rust просто не соберётся, потому что компилятор не позволит. И чтобы удовлетворить его требованием, надо будет поставить дополнительные обёртки вокруг переменных (там кстати тоже будет большой набор сложностей, но хотя бы в итоге получится скомпилировать).
_>>Кроме показанного Иваном можно вспомнить и про бесконечные unwrap. И ещё есть набор нюансов связанных не с реализацией логики, а исключительно с обслуживанием BC. WH>Так ты покажи. Я когда на rust код писал, что-то ничего страшного не заметил.
Ну сделай поиск, сколько unwrap'ов в твоём проекте? И если что, все эти вызовы являются чистым синтаксическим мусором, существующим исключительно для правильной работы BC.
_>>Будет в любом языке. И толщина этого талмуда зависит исключительно от применяемой архитектуры многотпоточности, а не от выбранного языка. Скажем при использование модели акторов талмуд будет скорее всего самым тоненьким (в любых языка). При использование модели синхронизации разделяемых данных барьерами талмуд будет однозначно толще (в любых языках). А самый толстый будет наверное в случае применения раличных lock-free инструментов (опять же в любых языках). WH>Талмуд для evmap: Не делай refresh внутри транзакции чтения.
Что-то ты пропустил часть инструкции, написанной у них же на первой странице: используйте mutex для записи данных из нескольких потоков. Ну а к этой инструкции соответственно можно приложить хороший талмудик на тему безопасной работы с мьютексами.
_>>Ты тут кстати действительно оказался прав. Я не внимательно посмотрел в прошлый раз: увидев в начале описание утверждение о том что это lock-free структура (и поверив в это), сразу пошёл в конец смотреть графики и т.п. И пропустил важнейший момент в середине текста. Оказывается эти умники предлагают в случае нескольких писателей в эту, как бы "lock-free" сущность, использовать мьютекс! Такого я точно не ожидал... Такая детская хрень не то что любые гарантии акторов выкинет, оно ещё и производительность убьёт (ради которой и страдают с неудобными lock-free сущностями). WH>А ты точно графики смотрел? WH>Линейный рост производительности чтения видел? WH>Да и запись не сильно уступает конкурентному словарю. А вот на чтении оно намного быстрее. WH>А на счёт гарантий тут всё просто. Делаем пишущий актор и кучу читающих. Всё будет прекрасно работать. WH>И вообще по жизни задачи, где нужно больше одного писателя почти не встречаются.
Да, это всё понятно. Просто я изначально подумал, что это более сложное решение. А данная ерунда делается элементарно каждым, прямо в своём коде, без использования каких-то библиотек. Тут даже CAS не требуется, достаточно атомарности изменения указателя (которая и так есть на существующих платформах). Ну и да, с моделью акторов эта хрень в любом случае не дружит, по целому ряду причин.
_>>Да элементарно даже на C + WinAPI. ))) WH>Без гарантий.
Повторюсь наверное уже в 10-ый раз (странно, что до тебя не могут дойти такие очевидные истины): мы получаем гарантии корректной многопоточности, при условии соблюдения некоторых простейших правил в своём коде. В данном контексте это будет звучать как-то типа "не использовать указатель после его отправки в PostMessage" (ну это помимо общепринятого "не использовать глобальные переменные") и т.п.
_>>С учётом того, что оказалось что evmap — это не lock-free, а требующая мьютекса хрень, думаю даже нет смысла обсуждать это. Т.е. да, некорректный код там не получишь — получишь тупо тормоза в лучше случае и дедлок в худшем. WH>На чтение lock-free. Да и больше одного писателя обычно не нужно. Так что и на запись lock-free. WH>А иметь lock-free чтение с уровнем изоляции serializable в очень многих задачах очень круто.
Про какой ещё уровень изоляции ты говоришь, если допускается только последовательная запись? ))) Это даже смешно обсуждать. )))
WH>Собственно почти все read-write lock’и можно заменить на evmap. Тем более что эту систему можно сделать вокруг любого объекта, а не только словаря.
Да, большинство решений на блокировках можно переписать без них, получив существенный рост в производительности. Но такой код становится на порядок сложнее (не по объёму, а по достижению его корректности), требует очень хороших знаний в данной области и большого времени на проработку. Так что развлекаться подобными делами имеет смысл только при реальной потребности в ультимативной производительности.
_>>Только вот BC принуждает их использовать даже в тех случаях, когда они по факту не нужны. Т.е. в C++ в этом коде будет просто переменная, а в Rust'е придётся мутить обёртку ради удовлетворения BC. WH>Что за хрень ты несёшь? В каком месте Option<Box<Data>> упоминается BC? Ты вообще про что? И заметь ты сам так ничего и не показал. WH>Ну и в С++ вместо Box будет один из многих смартпоинтеров, а nullable типы это в любом случае зло. Так что Option тоже нужен без вариантов.
Это ты специально отрезал конец моей фразы, в которой было чётко указано про какой пример я пишу, чтобы сделать вид, что речь была про Option<Box<Data>>? Какие-то детские игры...
_>>Ты же вот слился показать аналог простейшего C++ кода из нескольких строчек. Случаем не по той причине, что тебя BC послал с прямым аналогом? ))) WH>Это простейший говнокод. Любители динамической типизации тоже такой фигнёй страдают. Напишут кусок говнокода, который на языке со статической типизаций не написать и объявляют победу. WH>А на вопрос когда такой код может понадобиться, сразу сливаются. Прямо как ты.
Причём тут говнокод или нет? Это компилируемый, корректно работающий, и выдающий фиксированный результат код на языке со статической типизацией. Ты можешь повторить его на Rust или нет? Напрямую уже видно что нет, но давай обсудим какие модификации тебе необходимы, чтобы он всё же хотя бы собрался.
Здравствуйте, Иван Дубров, Вы писали:
_>>Что-то ты совсем заговорился. ))) Ты предложил рассмотреть в виде этой "особой ситуации" случай вылета приложения по исключению и при этом считаешь это неочевидным поведением, которое будет сложно отследить? ))) ИД>Нет, я не хочу вникать в нюансы, вот типа сегодня это main, а завтра -- функция внутри системы. Сегодня это исключение, а завтра -- ранний выход из функции по редкому условию (ну там, решили ошибку вернуть). ИД>В этом есть особенность Rust -- без unsafe он позволяет строго подмножество "безопасных" программ. Эта программа может быть безопасной в том виде, в котором она написана тобой, но это можно показать только внимательным анализом: например, что это main, который по исключению просто остановит программу. Что выход их функции может быть только по исключению, а не по какому-то условию, который кто-нибудь через полгода добавит в этот код.
Ну в целом это конечно же тоже подход к разработке, но лично мне он не нравится — слишком ограничивает.
Да и всё же данный пример был совсем не об этом: даже если подойти к разработке форумного примера, как к маленькой части большого и сложного приложения, то всё равно оно без проблем записывается (с помощью банального shared_ptr). А что мы видим на Rust?
_>>Эээ что? Откуда это ты изобрёл какую-то связь между Arc и иммутабельностью данных? ИД>Потому что ты сможешь получить изменяемую ссылку на внутренности Arc только в одном случае -- если это единственный экземпляр Arc. Иначе придётся использовать то, что в Rust называется "внутренняя изменяемость". Mutex -- один из примеров внутренней изменяемости (все они сводятся внутри к UnsafeCell, который в Rust является единственным не-UB вариантом иметь разделяемую изменяемую ссылку).
Во-первых можно жить и без такой ссылки: https://doc.rust-lang.org/std/sync/struct.Arc.html#examples — смотри второй пример. А во-вторых у нас есть unsafe. Так что в итоге решение всё же можно построить. Но сам видишь как сложно перевести на Rust даже абсолютно корректный код.
_>>И так, у нас есть конкретный, работающий пример на C++. ОК, Rust не позволяет переписать его дословно, потому что ему не нравится использование локальной переменной в других потоках. Ну ОК, сделай её не локальной переменной а выделенной в куче (Arc) и всё — в чём проблема то? Вся эта борьба с BC вообще не имеет никакого отношения к тому нюансу, который я хотел продемонстрировать этим примером. Т.е. да, Rust должен был "слиться", но намного позже и совсем в другом смысле. Я не ожидал, что у всех участников дискуссии возникнет проблема даже с банальным повтором такого простейшего кода. ИД>Потому что Rust требует отсутствие гонок данных.
Это на самом деле тоже иллюзия, даже без unsafe. Потому как гонки могут быть и в рамках одного потока (сопрограммы, лёгкие потоки и т.п.), а Rust как я понимаю это даже не пытается отслеживать.
ИД>Можно умозрительно показать, что компилятор может это статически доказать (используя borrow checker) только если все твои локальные и глобальные данные покрыты одним мьютексом. Иначе потенциально возможна ситуация, когда одни данные могут изменятся конкурентно под двумя разными мьютексами. ИД>Тут два варианта в Rust: ИД>1. Ты знаешь, что делаешь и ты можешь спрятать свою структуру данных за безопасным интерфейсом. ИД>2. Ты думаешь, что знаешь, что делаешь, но на самом деле нет. Тогда тебе лучше переформулировать свой код в стиле Rust. ИД>Поэтому нет смысла такие маленькие примеры рассматривать -- Rust требует другого подхода к проектированию систем. С одной стороны, проще писать "локальный" код -- можно не думаю, добавит ли этот ранний выход проблем с воооон теми потоками или нет ("fearless"), но сложнее проектировать систему "глобально" -- требуется хорошее понимание Rust. Но многие задачи укладываются в уже существующие парадигмы в Rust -- бери библиотеку, да используй.
Понятно, что с укладывающимися в его парадигму примерами, Rust справляется отлично. Весь вопрос был как раз в других примерах. И не думаю что этот мой код демонстрировал что-то особо странное (опять же сосредоточимся на вопрос с мьютексами, а не на проблеме с локальной переменной).
ИД>Да, Rust примет строгое подмножество корректных программ (т.е есть корректные программы, которые Rust без unsafe не примет) -- иначе не нужен был бы unsafe. Но те программы, которые он примет будут иметь отсутствие определённых эффектов (data races, etc).
Ну, т.к. удобный unsafe в Rust есть, то данный нюанс всё же не отменяет для меня (и таких как я) потенциалную возможность перехода на Rust. Но вносит существенный отрицательный настрой...
Здравствуйте, alex_public, Вы писали:
ИД>>Потому что Rust требует отсутствие гонок данных. _>Это на самом деле тоже иллюзия, даже без unsafe. Потому как гонки могут быть и в рамках одного потока (сопрограммы, лёгкие потоки и т.п.), а Rust как я понимаю это даже не пытается отслеживать.
Ты не понимаешь термины. Иван говорит об отсутствии _гонок данных_ (data race), а ты говоришь о гонках вообще.
The memory model defined in the C11 and C++11 standards uses the term "data race" for a race condition caused by potentially concurrent operations on a shared memory location, of which at least one is a write. A C or C++ program containing a data race has undefined behavior.
Так вот. В C++ это undefined behaviour, в rust — ошибка компиляции.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Да, это будет та самая "внутренняя изменяемость".
_>А во-вторых у нас есть unsafe. Так что в итоге решение всё же можно построить. Но сам видишь как сложно перевести на Rust даже абсолютно корректный код.
Да, это так. На мой взгляд, в Rust можно вести масштабную разработку силами неопытных разработчиков, но скорее всего, потребуются также весьма обученные знатоки Rust чтобы выстроить костяк системы. Умеющие "мыслить" как Rust. Во многих случаях, есть альтернативные способы выразить функциональность, и да, некоторые из них могут вызывать сильное неприятие (https://exyr.org/2018/rust-arenas-vs-dropck/, вектор с индексами, как пример).
_>Это на самом деле тоже иллюзия, даже без unsafe. Потому как гонки могут быть и в рамках одного потока (сопрограммы, лёгкие потоки и т.п.), а Rust как я понимаю это даже не пытается отслеживать.
Я про частный случай -- гонки данных. В общем случае да, гонки, конечно, возможны. Но даже в пределах одного потока Rust в какой-то степени отслеживает за счёт правила, что изменяемая ссылка никогда не может быть aliased (т.е нельзя получить две разные изменяемые ссылки на одно и то же место в памяти, это считается UB). Единственное исключение -- UnsafeCell (который и лежит в основе более высокоуровневых структур предоставляющих "внутренней изменяемости" -- Atomic*, RefCell, Cell, Mutex, и.т.д).
Здравствуйте, alex_public, Вы писали:
WH>>А как же гарантированное математикой отсутствие проблем? _>Ещё раз: отсутствие проблем гарантируется при соблюдение некоторых условий. И смысл этой "математики" тут в том, что мы заменяем сложные и неочевидные условия, на достаточно простые. Правда, при этом слегка сужая область применения.
Так и в случае с мьютексами правило очень простое.
Нужно захватывать мьютексы в одном порядке. А учитывая, что в 99% случаев код больше одного мьютекса не захватывает то и проблем не возникает.
_>BC в том виде, в каком он сейчас в Rust'е, не нужен в C++, т.к. он позволяет реализовать только некое подмножество корректных программ (многие случаи абсолютно корректного кода всё равно не пропускает), что противоречит концепциям C++.
Любая статическая типизация не пропускает некоторое множество корректных программ.
_>Ну а некое подмножество BC, позволяющее безопасно управлять памятью появилось в стандарте языка C++11 и было доведено до ума в C++14.
Ты про что? Только не говори, что ты семантику перемещения называешь BC. Ибо это даже не рядом. Да и перемещение в С++ сделано хреново. В rust переменная после перемещения становится не доступной. А в С++ остаётся зомби объект.
WH>>Это какие? _>Например сопрограммы (на уровне языка, со всеми вытекающими из этого вкусняшками),
В rust это тоже скоро будет.
_> контракты (которые на уровне языка — компилятор может использовать для оптимизации кода),
Какое отношение оно имеет к оптимизации?
_>вычисления на этапе компиляции (полноценный код с контейнерами, полиморфизмом и т.п.),
В rust есть макросы. Так что это всё детски сад.
_>Ну во-первых переписали пока не продукт, а его маленький кусочек.
Расчёт CSS это одна из важнейших частей данного продукта.
_>И думаю что если бы этот кусочек переписали бы на современный C++, то результат был бы в точности такой же.
За счёт чего? BC нет. Реализуемых компилятором трейтов Sync и Send нет.
WH>>От жеж лицемер. В С++ в том примере будет тоже самое. _>Тебе уже наглядно продемонстрировали, что не будет.
Мы говорим про Option<Box<Data>> на который ты возбудился.
Так что ещё как будет.
_>Тот пример кода на C++, что ты так не смог повторить, не требует никаких контейнеров. А его точный аналог на Rust просто не соберётся, потому что компилятор не позволит. И чтобы удовлетворить его требованием, надо будет поставить дополнительные обёртки вокруг переменных (там кстати тоже будет большой набор сложностей, но хотя бы в итоге получится скомпилировать).
Ещё раз. Когда такой код нужен?
Ты же говоришь, что постоянно такое пишешь. Но пример привести не можешь.
Почему?
WH>>Так ты покажи. Я когда на rust код писал, что-то ничего страшного не заметил. _>Ну сделай поиск, сколько unwrap'ов в твоём проекте?
Ни одного.
_>И если что, все эти вызовы являются чистым синтаксическим мусором, существующим исключительно для правильной работы BC.
Ты rust вообще не знаешь.
unwrap к BC вообще никакого отношения не имеет.
Он исключительно про обработку ошибок. Или точнее про игнорирование обработки ошибок.
Так что если ты видишь в коде unwrap это означает что код по-быстрому наговнокодили и потом нужно будет отдавать технический долг.
_>Что-то ты пропустил часть инструкции, написанной у них же на первой странице: используйте mutex для записи данных из нескольких потоков. Ну а к этой инструкции соответственно можно приложить хороший талмудик на тему безопасной работы с мьютексами.
1)В 99% случаях работать из множества потоков не нужно.
2)Не путай мьютексы в rust с мьютексами во всех остальных языках. В rust с мьютексом накосячить очень сложно.
_>Ну и да, с моделью акторов эта хрень в любом случае не дружит, по целому ряду причин.
Назови хоть одну причину, почему она не будет работать с акторами в rust'е? BC передаёт привет.
_>Повторюсь наверное уже в 10-ый раз (странно, что до тебя не могут дойти такие очевидные истины): мы получаем гарантии корректной многопоточности, при условии соблюдения некоторых простейших правил в своём коде. В данном контексте это будет звучать как-то типа "не использовать указатель после его отправки в PostMessage" (ну это помимо общепринятого "не использовать глобальные переменные") и т.п.
А в случае с rust за этими правилами следит компилятор. И ты не сможешь их нарушить.
_>Про какой ещё уровень изоляции ты говоришь, если допускается только последовательная запись? ))) Это даже смешно обсуждать. )))
Изоляция транзакции чтения от транзакции записи.
_>Причём тут говнокод или нет? Это компилируемый, корректно работающий, и выдающий фиксированный результат код на языке со статической типизацией. Ты можешь повторить его на Rust или нет? Напрямую уже видно что нет, но давай обсудим какие модификации тебе необходимы, чтобы он всё же хотя бы собрался.
Необходимо знать какую задачу ты решаешь.
Пока что видно что ты написал говнокод только ради того чтобы написать говнокод.
Статическая типизация она вообще всегда отвергает некоторое множество корректных программ. И чем сильнее типизация, тем больше ошибок она ловит и больше корректных программ отвергает.
Вот только нужны ли эти программы? Ни ты, ни любители динамической типизации, которые используют ровно этот же аргумент пользы от всех этих выкрутасов показать не в состоянии.
Если ты не можешь то может попросим помощь зала?
Расскажите, у кого были случаи, когда под одним мьютексом жило несколько объектов с разным временем жизни?
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, Иван Дубров, Вы писали:
_>>Он конечно возможно и некорректный, но исключительно в административном смысле. Потому как компилятор переварит его без единого возражения. Более того, он может скомпоновать этот код в красивую библиотечку, которую будут распространять без исходников (хотя на самом деле сейчас большинство народа не смотрит исходники даже если они открыты). Понятна моя мысль? ) ИД>Разумеется. Я не вижу смысла доводить это до какого-то абсолюта -- разработка в целом подразумевает какой-то уровень доверия. Цель не построить железобетонную защиту, а в большой степени освободить себя от определённого класса ошибок.
Это понятно, что должен быть некий набор базовых библиотек, которым доверяешь и т.п. Но здесь речь немного не о том. Основной нюанс в том, что приведённая мною функция не является ошибочной (в том смысле, что её использование не обязательно приводит к созданию некорректного кода)! Т.е. она вполне имеет право на жизнь скажем в какой-нибудь отладочной библиотеке (например для того чтобы побродить по структурам памяти, не влезая в их код) и т.п. Просто при этом она ещё и снимает все гарантии, так что при неверном использование она позволит компилятору создать некорректный код. И различных вариаций на тему подобных функций может быть множество...
В целом в этом нет ничего ужасного и подобное можно найти в большинстве языков. Печальной может быть только ситуация, когда за кодирование на Rust'е возьмётся некий "религиозный фанатик" языка, уверенный что компилятор точно даёт ему какие-то гарантии, если он не использовал в своём коде ключевого слова unsafe.
_>>А как ты поймёшь то, где у тебя unsafe код, а где нет? Вот я сделал вызов некой функции hack_rust (см. код в сообщениях выше) — это безопасно или нет? Как понять? А например использование std::mem::forget — это точно всегда безопасно? ))) ИД>unsafe код должен быть (да, административными методами) ограничен маленькой областью с безопасным интерфейсом.
Собственно это правильный подход не только для Rust'а, но и для любых языков.
_>>На самом деле для решения этой проблемы надо просто следовать определённым не сложным правилам. В любом языке. ИД>Нет, это не подтверждается историей разработки software, и, в частности CVE. Уже давно пора прекратить этот миф.
Эээ что? ) Подавляющее число CVE идут от переполнения буфера, от которого автоматической защитой может служить разве что проверка на размер при каждом обращение (если требуемая производительность это позволяет). В некоторых языках это дефолтная политика стандартных контейнеров, в некоторых нет, но в любом случае это всё выбор программиста, определяемый производительностью. И управление памятью в стиле Rust'а тут явно ничего не добавляет.
Здравствуйте, ·, Вы писали:
_>>Я думаю что очень многие C++ проекты начатые после 2014 используют безопасные подходы. Т.е. технически это можно было делать и намного раньше, но это было не формализировано, не стандартизировано и т.п. А с приходом C++14 это уже просто норма языка, которую не соблюдают разве что следуя традициям проекта начатого давным давно. Насчёт крупности даже не знаю — мне как-то лень искать какие крупные C++ проекты появились в последнее время. В моих проектах как раз всё безопасно (ну не считая взаимодействия с C библиотеками, но это вынужденное зло), но ты их точно не найдёшь в открытом виде, так что вряд ли может служить аргументом. ·>Т.е. существует где-то в ненаблюдаемой реальности. Сложно с таким спорить.
А я и не планировал кому-то что-то доказывать по данному вопросу. Единственное что у меня есть весьма обоснованные сомнения, что я один такой гений на планете, который осилил программирование на современном C++. )))
_>>Хы, ты совсем не понял аналогию. Смотри как выглядит в реальности. Можно чётко выделить 3 группы подходов в управление: _>>1. Полностью автоматическое и не требующее мозга для использование. Характеризуется пониженной производительностью и снижением области применения. _>>В управление памятью примером этого является сборщик мусора (например как в Java или ). ·>OutOfMemory можно в java получить? Можно. unsafe есть? Есть. native есть? Есть. Значит мозг таки требуется.
Что за unsafe в Java? Что за native? Откуда такие фантазии? )))
_>>В управление многопоточностью примером этого является модель акторов (например как в Erlang). ·>Дедлок возможен? Возможен. NIF есть? Есть. ETS есть? Есть. Значит мозг таки требуется. ·>Значит по-твоему "никаких гарантий". ·>Или давай раскрывай что конкретно гарантирует "Полностью автоматическое управление".
Ммм, а где ты в тексте этой моей аналогии увидел хоть слово про какие-то гарантии? Ты мне предлагаешь поспорить с голосами в твоей голове? )))
_>>2. Автоматическое управление (всё чётко проверяется компилятором), но требующее приложение мозга (программист должен указывать компилятору свойства объектов). Имеет нулевой оверхед и подходит для любой области. _>>В управление памятью примером этого являются "умные указатели" (как в Rust или C++). ·>А &-ссылки в C++ тоже запрещать надо для этого или как?
Ссылки C++ никак не относятся к механизму управления памятью — они её не выделяют и не освобождают.
·>Как вообще можно по куску кода понять — используется ли в данном коде "правильное управление памятью" или не очень?
Это ты про какой язык спрашиваешь, про Rust или C++ или ещё какой? )
_>>В управление многопоточностью подобного решения пока к сожалению не существует! ·>Суть в том, что rust _компилятор_ проверяет код и чётко гарантирует отсутствие data race. Иными словами, даёт safety guarantees от data race. Если проверку отключить явно (т.е. использовать unsafe), то понятное дело, не проверяет, и гарантии не даёт. КО.
Во-первых подход Rust'а к многопоточности абсолютно не автоматический. Т.е. если для управления памятью умные указатели unique_ptr/box инкапсулируют в себя выделения и освобождение памяти, то в области параллелизма раскидывать по коду мьютексы и вызывать их захват надо по прежнему руками. Т.е. уже нельзя говорить ни о каком автоматизме.
Во-вторых, одобренная компилятором программа, не гарантирует даже отсутствие гонок данных (не говоря уже о более сложных вещах — см. следующий пункт). И я здесь говорю даже не о явном использование unsafe, а о том что при отсутствие должного умения даже вполне корректные сущности (типа того же Atomic), которые компилятор спокойно допускает до многопоточного использования без всяких unsafe, способны наделать бед.
Ну и в третьих (хотя это пожалуй самое главное на практике) — даже если мы добьёмся отсутствия гонок с помощью расстановки мьютексов по коду, это ещё не гарантирует нам корректный многопоточный код. Какие-то гарантии могут дать только какие-то архитектурные приёмы, регламентирующие использование этих мьютексов. Но компилятор подобное отследить не в состояние.
·>Иными словами. Тезис в том, что для данного куска кода: ·>1. Если в этом коде нет unsafe ·>2. Если все используемые в этом коде зависимости safe.
Это что вообще значит в реалиях Rust'а? Вообще то там является нормой оборачивать некие unsafe куски в safe функции....
·>3. Если rust компилятор выдал "ок". ·>=> то код обладает safety guarantees для данного куска кода.
"safety guarantees"? ) Это ты из какого-то пресс-релиза что ли скопировал? ))) Мы вообще то на техническом форуме и обсуждаем конкретные технические вопросы, а не какую-то абстрактную маркетинговую чушь.
·>Подобное недоступно в C++ никакими техническими средствами, только если вручную внимательно просмотреть код глазками и убедиться в отсутствии всех "неправильных" конструкций. Например, вот код, очень похожий на твой:
·>
·>Как узнать что тут есть ошибка? ·>В rust такое тупо не скомпилится. ·>В этом и отличие.
Только пока что Rust не может скомпилировать и корректный вариант. В том смысле что никто из защитников языка не смог представить соответствующий код. Может ты сможешь? )))
_>>·>По поводу использования unsafe vs "правильный С++" — это опять словоблудие. Вот пришел тебе пулл реквест на review — в случае rust ты можешь сразу увидеть unsafe и насторожиться. Можно даже тулзы соответсвтвующим настроить, чтобы коммиты с unsafe реджектить или назначать более серьёзный review всей командой. В случае С++ любое изменение потенциально может поломать всё что угодно и нет никакого простого и однозначного способа отличить безопасные изменения от опасных. _>>Вот в том то и дело, что не сможешь увидеть. Потому что в Rust применение usafe блока не обязывает помечать функцию как unsafe. Так что по факту ты можешь просто вызвать в своём коде некую вполне себе прилично выгляющую safe функцию из какой-то посторонней библиотеки и тем самым неявно получить в коде своего приложения дикий unsafe код. Который на ревью никто не увидит. ·>Ровно то же и в java — вызываешь приличную функцию, а она дёргает native и всё крошит. В чём отличие-то?
Ну так значит и на Rust "нет никакого простого и однозначного способа отличить безопасные изменения от опасных", правильно? )))
_>>Более того, в стандартной библиотеке языка мы можем увидеть такие милые функции как например std::mem:forget, которые позволяют натворить много чего весёлого. Да, и если что, forget тоже не помечена как unsafe функция... ·> ·>
forget is not marked as unsafe, because Rust's safety guarantees do not include a guarantee that destructors will always run. For example, a program can create a reference cycle using Rc, or call process::exit to exit without running destructors. Thus, allowing mem::forget from safe code does not fundamentally change Rust's safety guarantees.
Ну ты же понимаешь, что это означает как минимум "Rust не гарантирует отсутствие утечек памяти и других ресурсов, управляемых через RAII"?
_>>Так что если реально хотим сидеть в безопасной зоне (хотя бы по памяти — про многопоточность даже заикаться не надо!), то на Rust тоже будет небольшой талмудик требований, а не одна строчка "без unsafe". ·>А что же тогда будет гарантией "безопасной многопоточности"? Что вообще значит "безопасная многопоточность" по-твоему? В каких системах в принципе есть безопасность?
Желательно получить такой инструмент, который брал бы на себя заботу обо всех технических нюансах, оставляя нам заботу только об ошибках в бизнес-логике. Из существующих подходов к этому ближе всего модель акторов. Но она к сожалению имеет существенные накладные расходы.
Здравствуйте, alex_public, Вы писали:
_>>>Хы, ты совсем не понял аналогию. Смотри как выглядит в реальности. Можно чётко выделить 3 группы подходов в управление: _>>>1. Полностью автоматическое и не требующее мозга для использование. Характеризуется пониженной производительностью и снижением области применения. _>>>В управление памятью примером этого является сборщик мусора (например как в Java или ). _>·>OutOfMemory можно в java получить? Можно. unsafe есть? Есть. native есть? Есть. Значит мозг таки требуется. _>Что за unsafe в Java?
sun.misc.Unsafe, jdk.internal.misc.Unsafe.
_>Что за native? https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.3.4
_>Откуда такие фантазии? )))
Откуда такое агрессивное невежество?
_>>>В управление многопоточностью примером этого является модель акторов (например как в Erlang). _>·>Дедлок возможен? Возможен. NIF есть? Есть. ETS есть? Есть. Значит мозг таки требуется. _>·>Значит по-твоему "никаких гарантий". _>·>Или давай раскрывай что конкретно гарантирует "Полностью автоматическое управление". _>Ммм, а где ты в тексте этой моей аналогии увидел хоть слово про какие-то гарантии? Ты мне предлагаешь поспорить с голосами в твоей голове? )))
Что ты хотел показать этой аналогией?
_>>>2. Автоматическое управление (всё чётко проверяется компилятором), но требующее приложение мозга (программист должен указывать компилятору свойства объектов). Имеет нулевой оверхед и подходит для любой области. _>>>В управление памятью примером этого являются "умные указатели" (как в Rust или C++). _>·>А &-ссылки в C++ тоже запрещать надо для этого или как? _>Ссылки C++ никак не относятся к механизму управления памятью — они её не выделяют и не освобождают.
Зато они позволяют на ровном месте обратиться к освобождённой памяти.
_>·>Как вообще можно по куску кода понять — используется ли в данном коде "правильное управление памятью" или не очень? _>Это ты про какой язык спрашиваешь, про Rust или C++ или ещё какой? )
В данном месте, вроде бы очевидно, я говорил о С++.
_>>>В управление многопоточностью подобного решения пока к сожалению не существует! _>·>Суть в том, что rust _компилятор_ проверяет код и чётко гарантирует отсутствие data race. Иными словами, даёт safety guarantees от data race. Если проверку отключить явно (т.е. использовать unsafe), то понятное дело, не проверяет, и гарантии не даёт. КО. _>Во-первых подход Rust'а к многопоточности абсолютно не автоматический. Т.е. если для управления памятью умные указатели unique_ptr/box инкапсулируют в себя выделения и освобождение памяти, то в области параллелизма раскидывать по коду мьютексы и вызывать их захват надо по прежнему руками. Т.е. уже нельзя говорить ни о каком автоматизме.
Ну и не говори об автоматизме. Кто ж тебя заставляет?
_>Во-вторых, одобренная компилятором программа, не гарантирует даже отсутствие гонок данных (не говоря уже о более сложных вещах — см. следующий пункт). И я здесь говорю даже не о явном использование unsafe, а о том что при отсутствие должного умения даже вполне корректные сущности (типа того же Atomic), которые компилятор спокойно допускает до многопоточного использования без всяких unsafe, способны наделать бед.
Гарантирует. Atomic не создаёт гонки данных. Data race даёт неопределённое поведение, т.е. нельзя ничего утверждать о поведении такой программы. Поведение atomic строго определено и можно просчитать возможные исходы что может получиться в результате.
_>Ну и в третьих (хотя это пожалуй самое главное на практике) — даже если мы добьёмся отсутствия гонок с помощью расстановки мьютексов по коду, это ещё не гарантирует нам корректный многопоточный код.
И что?
_>Какие-то гарантии могут дать только какие-то архитектурные приёмы, регламентирующие использование этих мьютексов. Но компилятор подобное отследить не в состояние.
Каким образом эти гарантии будут обеспечиваться?
_>·>2. Если все используемые в этом коде зависимости safe. _>Это что вообще значит в реалиях Rust'а? Вообще то там является нормой оборачивать некие unsafe куски в safe функции....
В реалиях, это означает, что безопасность помеченных unsafe кусков приходится гарантировать другими средствами, оставаясь без гарантий компилятора.
_>·>3. Если rust компилятор выдал "ок". _>·>=> то код обладает safety guarantees для данного куска кода. _>"safety guarantees"? ) Это ты из какого-то пресс-релиза что ли скопировал? ))) Мы вообще то на техническом форуме и обсуждаем конкретные технические вопросы, а не какую-то абстрактную маркетинговую чушь.
Ага. Очень техническое возражение.
_>·>В этом и отличие. _>Только пока что Rust не может скомпилировать и корректный вариант. В том смысле что никто из защитников языка не смог представить соответствующий код. Может ты сможешь? )))
Ты таки задачу так и не обозначил. А переводить некий говнокод с одного яп на другой — бессмысленное занятие.
_>·>Ровно то же и в java — вызываешь приличную функцию, а она дёргает native и всё крошит. В чём отличие-то? _>Ну так значит и на Rust "нет никакого простого и однозначного способа отличить безопасные изменения от опасных", правильно? )))
Если принять за истину твою т.з., то да, так, и Луна сделана из сыра.
_>Ну ты же понимаешь, что это означает как минимум "Rust не гарантирует отсутствие утечек памяти и других ресурсов, управляемых через RAII"?
Ну да. И?
_>·>А что же тогда будет гарантией "безопасной многопоточности"? Что вообще значит "безопасная многопоточность" по-твоему? В каких системах в принципе есть безопасность? _>Желательно получить такой инструмент, который брал бы на себя заботу обо всех технических нюансах, оставляя нам заботу только об ошибках в бизнес-логике. Из существующих подходов к этому ближе всего модель акторов. Но она к сожалению имеет существенные накладные расходы.
В чём выражается безопасность акторов-то? Что значит "безопасная многопоточность"-то?
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, alex_public, Вы писали:
_>Но здесь речь немного не о том. Основной нюанс в том, что приведённая мною функция не является ошибочной (в том смысле, что её использование не обязательно приводит к созданию некорректного кода)!
С точки зрения Rust, как экосистемы, этот код некорректен.
Этот CVE по сути означает возможность приведения ссылки к произвольному типу, не прибегая к безопасному интерфейсу. То есть просто возможность сделать, грубо говоря, C-style приведение указателя считается за уязвимость.
_>Т.е. она вполне имеет право на жизнь скажем в какой-нибудь отладочной библиотеке (например для того чтобы побродить по структурам памяти, не влезая в их код) и т.п. Просто при этом она ещё и снимает все гарантии, так что при неверном использование она позволит компилятору создать некорректный код. И различных вариаций на тему подобных функций может быть множество...
Ну нет, так не нельзя (понятно, что физически тебя никто не остановит -- см. actix gate, хотя и там вроде всё счастливо закончилось). Нужна подобная отладочная функция -- делай её unsafe, с комментарием какие гарантии ты перекладываешь на разработчика.
Это один из ключевых механизмов обеспечения безопасности в Rust -- safe интерфейс не должен позволять использование освобождённой памяти, висящие ссылки, гонки данных и прочий undefined behavior.
Здравствуйте, WolfHound, Вы писали:
_>>Ну вот, снова очередной явный слив. Тебе прямо показали компилируемый код, но ты попробовал сделать вид, что не заметил его. Уже во второй раз в данной дискуссии. WH>Я увидел там unsafe без создания safe интерфейса. WH>Нормальные люди так не делают.
А можно узнать, где есть формулировка этого самого safe интерфейса? Т.е. уже понятно, что на уровне компилятора данного определения не существует и проверять он его не умеет. Но возможно есть какой-то официальный талмудик, в котором будут определения? )))
_>>Что такое "дамп зависшего приложения" и в каких условиях твои приложения выдают эту сущность пользователю? WH>Зависит от того что за приложение.
Да хоть один любой пример приведи)))
_>>Это всё мелкие детали. Ключевой момент в том, что компилятор свободно позволяет создавать некорректные приложения, и исправляются ошибки как и обычно руками программиста. Т.е. в этом нет ничего плохого или необычного — всё как всегда и как и во всех языках. Ненормальна тут только твоя вера в некую магию одного языка. WH>У меня есть чёткое понимание того какие классы ошибок устраняются и как именно это происходит.
Вооот! Это уже деловой разговор. И у меня тоже есть чёткое понимание этого вопроса в Rust'а — я вполне согласен, что есть целые классы ошибок, от которых он защищает. Но вот в области многопоточности Rust точно не способен дать гарантии корректности.
_>>Хы, вот в том то и дело, что в реальности мьютексы чаще используют не для защиты конкретного ресурса, а для различных синхронизаций. Т.к. это один из самых базовых и низкоуровневых примитивов синхронизации. Например conditional variable работают через мьютексы (и кстати Rust тут не исключение https://doc.rust-lang.org/std/sync/struct.Condvar.html) и ещё много чего. WH>Причём тут conditional variable? WH>Они используются для того чтобы сообщить что данные которые защищает мьютекс изменились. WH>Обрати внимание на сигнатуры. Метод wait поглощает MutexGuard для того чтобы освободить его. И возвращает новый MutexGuard после того как он снова захватил ресурс. WH>
WH>В других языках происходит то же самое. Просто они корректность работы с мьютексами не контролируют.
Что-то я не понял с чем ты тут споришь собственно говоря. Может просто из принципа, что нельзя соглашаться, а надо обязательно хоть в чём-то возразить? ))) В данном примере мьютекс используется грубо говоря для отсылки сообщения из одного потока в другой (это собственно говоря основной предназначение таких переменных, причём чаще всего уведомляют не один поток, а несколько), а не для разделения каких-то рабочих данных между несколькими потоками.
Кстати говоря, в winapi для подобной функциональности есть даже отдельный объект ядра (Event), так что теоретически какие-то реализации conditional variable могли бы работать под виндой и не через мьютексы. Однако в большинстве реализации языков и фреймворков предпочитают использовать традиционную схему с мьютексами, вне зависимости от платформы.
Ну и это естественно не единственное применение мьютексов в области синхронизации, не укладывающееся в схему "охрана куска данных".
_>>Xa, xa. ))) Вообще то спинлоки давным давно встроены в реализации тех самых системных мьютексов, на большинстве современных ОС. Это сделано для оптимизации — при попытке захвата мьютекса поток в начале некоторое краткое время работает по схеме спинлока и только потом идёт вызов кода ядра, блокирующий поток. Так что говорить что спинлок — это альтернатива системным мьютексами весьма забавно. ))) WH>А почему такая оптимизация работает? Да по тому, что нормальные люди захватывают мьютекс на несколько десятков инструкций.
Нет, они работают потому, что часто мьютекс может быть просто свободен (ждать не надо вообще) и соответственно если сразу уходить в ядро, то переключение контекста будет сделано впустую. Если же вызывать этот код только при занятом мьютексе, то он вполне оправдан, т.к. переключать контекст всё равно надо.
WH>Ну и ты тут начал говорить про реализации, которые сразу в ядро лезут.
Про "сразу" я не говорил. Я говорил про то, что из-за факта хождения в ядро код на мьютексах может быть не самым эффективным. Но при этом конечно же существенно проще (если есть умение не нарваться на дедлоки).
_>>Ну так, когда я заговорил о гонках данных в рамках одного системного потока (что легко происходит при работе с различными зелёными потоками, сопрограмми и т.п.), то ты попытался резко уйти от этой темы, сказав что я слишком широко трактую понятие гонок (хотя на самом деле это стандартная трактовка). Возможно это было потому, что Rust ничего не предлагает в данной области? ))) WH>Тебя носит из стороны в сторону. Чёрт тебя разберет, что ты там имел в виду. WH>Гонками можно назвать и порядок прихода сообщений в очередь. При таком определении гонки и в эрланге есть.
Требования на упорядоченность сообщений в модель акторов не входит, так что никто на неё и не рассчитывает. А вот обычный, на первый взгляд линейный код (ведь сопрограммы и т.п. как раз и применяют для придания асинхронному коду линейного вида), в рамках одного системного потока обычно кажется защищённым от гонок. В то время как это на самом деле не так.
WH>А если мы тупо делаем зелёные потоки и свой планировщик, то для rust'а это ничем не будет отличаться от системных потоков. WH>Будут работать всё те же механизмы. WH>А то, что тебе нужно это объяснять много говорит о том насколько хорошо ты понимаешь rust.
И каким образом компилятор поймёт какие участки кода возможны для "параллельного" исполнения? ) В реальности это может быть произвольный код грубо говоря между двумя вызовами "await" (который кстати может быть ещё и не только явный, но и внутри других функций).
WH>>>Генерируем обёртку и работаем через неё. Можно даже сделать интерфейс, который делает объекты неразличимыми. _>>Так это изменит тип объекта, сломав всю дальнейшую работу системы. WH>Читай выделенное.
Так если код дальше будет банально требовать на входе переменную какого-то фиксированного типа, а ты подсунешь другой тип (пускай и с аналогичными методами), то ты думаешь этот код заработает? Это возможно только если весь дальнейший код написан в стиле шаблонов, чего тебе никто не обещал (и вообще редко бывает в небиблиотечном коде).
WH>>>А теперь ты отвечай, зачем нужен такой идиотизм. _>>Эм, у тебя короткая память? ))) Вообще то данный момент возник не из неоткуда, а из вполне конкретного обсуждения реализации гарантированного контроля доступа (например на предмет проверки захвачен мьютекс или нет) к переменной в динамических языках. В общем то очевидная вещь, просто ты неожиданно сказал что и без динамической типизации легко это повторишь... WH>На практике такого вообще не бывает. WH>Если ты создаёшь тип, объекты которого нужно разделять между потоками, то объекты этого типа без защиты вообще не нужны. WH>Короче, с твоей стороны очередной высосанный из пальца говнокод.
Эээ что? Какой ещё тип для разделения между потоками? Ты похоже уже привык к Rust'у и не можешь мыслить по другому (хотя как-то быстро это у тебя произошло, после Nemerle). В нормальном языке нам не надо плодить специальный тип для разделения данных — мы просто работаем с существующими переменными самых обычных рядовых типов.
_>>Но вот ситуация с тем, что потокам могут понадобиться данные с разным временем жизни — это абсолютно нормальное и повседневное явление. WH>Раз это явление повседневное то ты должен легко дать ответ на этот вопрос. WH>Но ты уже несколько сообщений вертишься. WH>Почему? WH>Может по тому, что ты ради спора насосал говнокод из пальца, а реальных примеров в твоей практике никогда не было?
На какой вопрос то?
_>>Ну да, т.е. вся индустрия идиоты и проектируют свои интерфейсы для одного, а используют их совсем для другого. Конечно, конечно. ))) WH>У индустрии не было borrow checker'а. А изначально и шаблонов не было. Так что она тупо не могла сделать нормальный интерфейс мьютекса. WH>А как только появился язык с BC и шаблонами так сразу мьютекс начал защищать данные. WH>И никто против этого слова не сказал. Наоборот, все этому очень радуются.
Ха, BC в данной реализации мьютексов служит исключительно для предотвращения сохранения ссылок (хотя указатель сохранить никто не мешает, как мы видели) на данные под мьютексом. А для собственно реализации подобного мьютекса достаточно языка с наличием дженериков и RAII. Таких языков в данный момент не мало, но что-то не припомню именно такой реализации мьютекса где-то ещё.
Здравствуйте, WolfHound, Вы писали:
WH>Собственно rust по тому и возник что ребята из мозила хотели писать очень параллельный код но на С++ не получалось. Программа разлеталась на куски. А на rust получилось.
Здравствуйте, WolfHound, Вы писали:
WH>Помню, когда C# только появился, про него тоже кричали ну и где написанный на нём софт? WH>Ну и где сейчас эти крикуны?
Ну и где написанный на нём софт? Кроме Paint.NET ничего хорошего я не видел
R>>что бы и под андроид и под иос и под винду и под линукс, как это есть у того же С++ WH>Когда С++ появился напомнить?
Расту 8 лет уже, как никак. На шарпе в этом возрасте уже во всю куячили в корпоративном секторе
C++ — ну, я затрудняюсь сказать, что в те годы было на плюсах написано. Кстати, какую дату возьмем за ДР C++?
Навскидку — UI библиотеки — wxWidgets (1992), Qt (1996), MFC (1992)
Независимые компиляторы — появились в основном в начале 90ых — Digital Mars С++, VisualAge C++, Watcom C++, Borland C++, Microsoft Visual C++. Для раста есть что-то подобное?
Здравствуйте, ·, Вы писали:
ИД>>>Потому что Rust требует отсутствие гонок данных. _>>Это на самом деле тоже иллюзия, даже без unsafe. Потому как гонки могут быть и в рамках одного потока (сопрограммы, лёгкие потоки и т.п.), а Rust как я понимаю это даже не пытается отслеживать. ·>Ты не понимаешь термины. Иван говорит об отсутствии _гонок данных_ (data race), а ты говоришь о гонках вообще.
То, что я описал, — это классические гонки данных, так что не надо тут глупости писать. Или тебе конкретный код показать что ли? )))
·>
·>The memory model defined in the C11 and C++11 standards uses the term "data race" for a race condition caused by potentially concurrent operations on a shared memory location, of which at least one is a write. A C or C++ program containing a data race has undefined behavior.
·>Так вот. В C++ это undefined behaviour, в rust — ошибка компиляции.
Детский сад какой-то. Ты вообще понимаешь в чём собственно проблема с параллельным доступом к одной памяти? В чём смысл атомарности и нужна ли она например для int'ов? Как это всё решает например CAS, который доступен в том же C++11 даже без ассемблера и как это соотносится с твоей цитатой о неопределённом поведение? Может всё же стоит понимать как оно внутри работает, а не рассуждать как о чёрном ящике?
Да, и Rust в этом смысле ничем не отличается от C++ и других системных языков: https://doc.rust-lang.org/nomicon/races.html — там вроде как есть и про Undefined Behavior и про то, что гарантий нет (причём речь не про unsafe).
Ну т.е. надо написать просто чуть больше кода. И оно в итоге даже будет внешне выглядеть как safe Rust...
_>>А во-вторых у нас есть unsafe. Так что в итоге решение всё же можно построить. Но сам видишь как сложно перевести на Rust даже абсолютно корректный код. ИД>Да, это так. На мой взгляд, в Rust можно вести масштабную разработку силами неопытных разработчиков, но скорее всего, потребуются также весьма обученные знатоки Rust чтобы выстроить костяк системы. Умеющие "мыслить" как Rust. Во многих случаях, есть альтернативные способы выразить функциональность, и да, некоторые из них могут вызывать сильное неприятие (https://exyr.org/2018/rust-arenas-vs-dropck/, вектор с индексами, как пример).
Согласен. Хотя в каком-то смысле это касается всех языков, просто тут особенно выражено (во всяком случае пока Rust не стал мейнстримом, если это конечно вообще произойдёт).
Для меня пока не однозначен вопрос о том, достаточна ли цена за подобные неудобства... Т.е. я вот чувствую, что вполне мог бы их пережить при желание, но не чувствую что получаемые в итоге бонусы того стоят.
_>>Это на самом деле тоже иллюзия, даже без unsafe. Потому как гонки могут быть и в рамках одного потока (сопрограммы, лёгкие потоки и т.п.), а Rust как я понимаю это даже не пытается отслеживать.
ИД>Я про частный случай -- гонки данных. В общем случае да, гонки, конечно, возможны. Но даже в пределах одного потока Rust в какой-то степени отслеживает за счёт правила, что изменяемая ссылка никогда не может быть aliased (т.е нельзя получить две разные изменяемые ссылки на одно и то же место в памяти, это считается UB). Единственное исключение -- UnsafeCell (который и лежит в основе более высокоуровневых структур предоставляющих "внутренней изменяемости" -- Atomic*, RefCell, Cell, Mutex, и.т.д).
Эмм, зачем такие сложности? Обычная глобальная переменная и всё — Rust же спокойно с ними работает. Да, он ругается на доступ к ней из нескольких потоков без unsafe, но тут то у нас будет один поток, так что никаких претензий. Единственно что я не в курсе есть ли сейчас в Rust'е библиотека сопрограмм (типа Boost.Coroutine2), чтобы можно было попробовать записать такой код. В самом языке сопрограмм почему-то (странно для вроде как современного языка — уже даже в древнем C++ их сейчас вводят на уровне языка, а не библиотеки) нет.