Идеология разная. В случае C# есть контекст, где использовать unsafeнельзя, в случае Rust такого нет. То есть в C# разделение на safe и unsafe используется для безопасности, а в Rust — исключительно для удобства программиста.
Arc не отвечает за синхронизацию доступа к объекту между потоками, а только за его удаление.
Это детали. Может не Arc, а Mutex<Arc> или ещё что-то такое.
Управляемый объект может быть организован так, что операции над ним потокобезопасны (например, обертка над библиотечным).
Да, может. И в случае именно Rust от такого объекта ожидается, что эта его потокобезопасность будет выражена в API этого объекта. Потому что Rust содержит необходимые средства для этого. Если API объекта не сообщает компилятору, что его можно свободно мутировать, то вам придётся сделать над объектом обёртку с unsafe кодом. И дальше вы сможете работать с объектом через эту обёртку без обращения к unsafe.
Если у вас многопоточное приложение, то каким-то образом вы должны синхронизировать потоки. Именно это и делает Arc, так что это, вообще говоря, не оверхед, а необходимая функциональность. Если же у вас какой-то свой хитрый способ синхронизации, то стоит подумать о передекомпозиции таким образом, чтобы явно выделить эту синхронизацию и инкапсулировать в неё unsafeы.
Это известный миф про Rust. Рациональная основа мифа состоит в том, что некоторые структуры данных, например, двусвязный список, проблематично реализовать без использования unsafe.
Иррациональная же (собственно мифологическая) часть — это то, что если вы в Rust-программе использовали unsafe, то это, якобы, неправильно. Возможно, играет роль схожесть ключевого слова с unsafe C#/.NET, которое и в самом деле является не столько частью языка/платормы, сколько расширением, и по-умолчанию недоступно.
Но в Rust unsafe — неотъемлимая часть языка, и есть немало сценариев, когда использование unsafe кода абсолютно нормально и не является признаком пахнущего кода. Реализация базовых контейнеров в их числе.
Нет, в C# lock мне писать бы вообще не пришлось. В смысле я бы скорее всего вообще не сообразил, что он нужен. Получился бы ошибочный, но короткий код, при этом ошибка в реальности воспроизводилась бы редко, а то и вообще никогда.
если b() вернет null, то вы получите NullReferenceException в рантайме
Всё правильно, там и должен быть NullReferenceException. В чём ошибка?
Пробовали ли вы написать на Rust функцию, которая принимает функцию, которая принимает функцию? И чтобы первая функция упаковывала переданную ей функцию вглубь структуры данных для последующего неоднократного коллбэка. И потом вызвать всё это с хитрыми замыканиями. Я пробовал, и код получается весьма перегруженный лайфтаймами и тому подобным. Не скажу, что результат неприемлем — совсем нет — но достаточно громоздок, чтобы начать избегать передавать функции в функции.
Использовали ли вы RefCell? А Mutex? А Mutex<RefCell>? А сочетание всего этого с Option? Код, который в C# или там Python выглядел бы как a.b().c начинает выглядеть примерно как a.lock().unwrap().b().as_ref().unwrap().c
На Rust действительно приятно писать код. Он действительно является привильно сделанным Си. Это всё правда. Но если вы можете конкретную штуку написать на чём-то другом — ну, например, F# — лучше писать на другом. Возможно, вы получите меньше удовольствия, но код точно будет существенно короче.
> Короче, долгая, нудная и часто неблагодарная работа.
И предлагается сделать её ещё более нудной? (По-моему, тест и вообще код, содержащий конкретные значения вроде `2`, `'a'`, `«Dr. Jones»` наконец, гораздо менее нудный, чем не содержащий таковых.)
Что это антипаттерн, и что в современном C# есть средства получше. Конечно, в public API нужно использовать хотя бы и антипаттерн, если это именно то, чего ждут пользователи этого API.
Но за исключением указанной выше ситуации вместо IDisposable лучше просто сделать метод Dispose(), а лучше метод с более конкретным названием. Не знаю, стоит ли такое упоминать, но класс должен быть sealed.
Конечно, раз уж мы сделали класс sealed и всегда зовём Dispose() явно, не полагаясь на финалайзер, то можно и : IDisposable подписать — просто чтобы использовать using. Но тогда появляется соблазн прикастить его где-нибудь к IDisposable, вернувшись таким образом к антипаттерну, а using на самом деле прекрасно заменяется на статический метод вроде T With<T>(Func<MyDisposableClass, T>), причём в некоторых случаях можно даже скрыть конструктор класса, оставив только этот метод.
А в некоторых случаях можно пойти ещё дальше, и вообще избавиться от класса, оставив только функцию With, которая будет выглядеть как-то так: T With<T>(Func<Resource1, Resource2, Resource3, T>)
Естественно, это не их проблема. Это _моя_ проблема. Но деньги-то плачу я. А когда я говорю «ок, вот вам деньги, давайте, решайте мою проблему», то стриминговые сервисы мне говорят «не, у нас-то проблем нет, так что и решать нечего, но ты давай плати».
> Как раз-таки потребители, осознавшие бессмысленность беспокойства, что что-то не хранится у них физически, и позволили стримингам и прочим сервисам по подписке взлететь.
Дело вовсе не в месте хранения, а в наличии/отсутствии полноценного доступа. Могу ли я, например, вырезать отдельный кадр из купленного фильма. Подправить субтитры? Заменить аудидорожку на авторский одноголосый перевод? И т. д.
Одна из проблем со всеми этими стриминговыми сервисами (которой не было у VHS, DVD и т. п) — ты платишь не за контент, а за _возможность потребить контент_.
То есть вы против политики на Хабре, но при этом, нисколько не смущаясь, заявляете, что «новость про матч Испания — Россия является позитивной»? Вы же сами таким вбросом провоцируцете перевод в плоскость политики. Неужели нельзя выражаться более аккуратно?
Вы говорите о разных фотонах: один про фотон-частицу, другой про фотон-квазичастицу. Строго говоря, первых в стекле вообще нет: фотон — это квант свободного эм поля, а в стекле оно сильно взаимодействует с веществом, и квантуется, соответственно, иначе.
Идеология разная. В случае C# есть контекст, где использовать
unsafe
нельзя, в случае Rust такого нет. То есть в C# разделение на safe и unsafe используется для безопасности, а в Rust — исключительно для удобства программиста.Это детали. Может не
Arc
, аMutex<Arc>
или ещё что-то такое.Да, может. И в случае именно Rust от такого объекта ожидается, что эта его потокобезопасность будет выражена в API этого объекта. Потому что Rust содержит необходимые средства для этого. Если API объекта не сообщает компилятору, что его можно свободно мутировать, то вам придётся сделать над объектом обёртку с unsafe кодом. И дальше вы сможете работать с объектом через эту обёртку без обращения к
unsafe
.Если у вас многопоточное приложение, то каким-то образом вы должны синхронизировать потоки. Именно это и делает
Arc
, так что это, вообще говоря, не оверхед, а необходимая функциональность. Если же у вас какой-то свой хитрый способ синхронизации, то стоит подумать о передекомпозиции таким образом, чтобы явно выделить эту синхронизацию и инкапсулировать в неёunsafe
ы.Это известный миф про Rust. Рациональная основа мифа состоит в том, что некоторые структуры данных, например, двусвязный список, проблематично реализовать без использования
unsafe
.Иррациональная же (собственно мифологическая) часть — это то, что если вы в Rust-программе использовали
unsafe
, то это, якобы, неправильно. Возможно, играет роль схожесть ключевого слова сunsafe
C#/.NET, которое и в самом деле является не столько частью языка/платормы, сколько расширением, и по-умолчанию недоступно.Но в Rust
unsafe
— неотъемлимая часть языка, и есть немало сценариев, когда использованиеunsafe
кода абсолютно нормально и не является признаком пахнущего кода. Реализация базовых контейнеров в их числе.Нет, в C# lock мне писать бы вообще не пришлось. В смысле я бы скорее всего вообще не сообразил, что он нужен. Получился бы ошибочный, но короткий код, при этом ошибка в реальности воспроизводилась бы редко, а то и вообще никогда.
Всё правильно, там и должен быть NullReferenceException. В чём ошибка?
Что-то больно у вас легко всё.
Пробовали ли вы написать на Rust функцию, которая принимает функцию, которая принимает функцию? И чтобы первая функция упаковывала переданную ей функцию вглубь структуры данных для последующего неоднократного коллбэка. И потом вызвать всё это с хитрыми замыканиями. Я пробовал, и код получается весьма перегруженный лайфтаймами и тому подобным. Не скажу, что результат неприемлем — совсем нет — но достаточно громоздок, чтобы начать избегать передавать функции в функции.
Использовали ли вы
RefCell
? АMutex
? АMutex<RefCell>
? А сочетание всего этого сOption
? Код, который в C# или там Python выглядел бы какa.b().c
начинает выглядеть примерно какa.lock().unwrap().b().as_ref().unwrap().c
На Rust действительно приятно писать код. Он действительно является привильно сделанным Си. Это всё правда. Но если вы можете конкретную штуку написать на чём-то другом — ну, например, F# — лучше писать на другом. Возможно, вы получите меньше удовольствия, но код точно будет существенно короче.
И предлагается сделать её ещё более нудной? (По-моему, тест и вообще код, содержащий конкретные значения вроде `2`, `'a'`, `«Dr. Jones»` наконец, гораздо менее нудный, чем не содержащий таковых.)
Что это антипаттерн, и что в современном C# есть средства получше. Конечно, в public API нужно использовать хотя бы и антипаттерн, если это именно то, чего ждут пользователи этого API.
Но за исключением указанной выше ситуации вместо
IDisposable
лучше просто сделать методDispose()
, а лучше метод с более конкретным названием. Не знаю, стоит ли такое упоминать, но класс должен быть sealed.Конечно, раз уж мы сделали класс sealed и всегда зовём
Dispose()
явно, не полагаясь на финалайзер, то можно и: IDisposable
подписать — просто чтобы использоватьusing
. Но тогда появляется соблазн прикастить его где-нибудь кIDisposable
, вернувшись таким образом к антипаттерну, аusing
на самом деле прекрасно заменяется на статический метод вродеT With<T>(Func<MyDisposableClass, T>)
, причём в некоторых случаях можно даже скрыть конструктор класса, оставив только этот метод.А в некоторых случаях можно пойти ещё дальше, и вообще избавиться от класса, оставив только функцию
With
, которая будет выглядеть как-то так:T With<T>(Func<Resource1, Resource2, Resource3, T>)
Реализовывать IDisposable без гугла — это уровень джуниора. Миддлу неплохо бы понимать, что IDisposable реализовывать вообще не надо.
Естественно, это не их проблема. Это _моя_ проблема. Но деньги-то плачу я. А когда я говорю «ок, вот вам деньги, давайте, решайте мою проблему», то стриминговые сервисы мне говорят «не, у нас-то проблем нет, так что и решать нечего, но ты давай плати».
Дело вовсе не в месте хранения, а в наличии/отсутствии полноценного доступа. Могу ли я, например, вырезать отдельный кадр из купленного фильма. Подправить субтитры? Заменить аудидорожку на авторский одноголосый перевод? И т. д.