The OpenNET Project / Index page

[ новости /+++ | форум | теги | ]



Индекс форумов
Составление сообщения

Исходное сообщение
"Выпуск языка программирования Rust 1.55"
Отправлено Ordu, 12-Сен-21 19:23 
>>> Во-первых, не так уж и редко.
> Приведите примеры когда это нужно.

Всё что угодно большое ты не будешь передавать "по-значению" без передачи владения, которая позволит избежать копирования. Если владение не передаётся, то компилятор вынужден создавать копию, дабы не было бы двух одновременных ссылок. Пример, может не очень "необходимого", но очень удобного -- это "типы-конструкторы":

let win = WindowBuilder::new()
    .size(800, 600)
    .title("Хелловорлд")
    .build();

Тут цепочка вызовов после new. new создаёт объект типа WindowBuilder, этот объект затем передаётся в size, где он модифицируется и возвращается, потом передаётся в title, где он модифицируется и возвращается оттуда, после чего он передаётся в build, который на основании значения типа WindowBuilder строит объект типа Window. Понятно, что при желании можно разорвать цепочку вызовов, сохранить промежуточный результат WindowBuilder в переменной, сделать что-нибудь ещё, потом продолжить. Ноль мутабельности, ноль ссылок. Никому не надо связываться со ссылками, потому что... а нахрена? Паттерн с передачей "по значению" с передачей владения моментально виден в документации. Скажем, вижу я в документации метод хештаблички into_values(self)->/*что-тотам*/,  я вижу, что self передаётся по значению, и моментально понимаю, что это _деструктивное_ преобразование хештаблички во что-то там, что судя по имени функции содежит все значения в хеш-табличке. После вызова её у меня не останется на руках исходной хеш-таблички.

Или, допустим, я вижу String::from_utf8(vec: Vec<u8>) -> Result<String, FromUTF8Error>, я моментально знаю, что from_utf8 "съедает" Vec<u8>, и даже не заглядывая в доки и сорцы, я спорить готов, что from_utf8 не выделяет память под новую строку, он повторно использует память Vec<u8>.

Или, скажем, я вижу конструктор BufReader<R>::new(inner: R) -> BufReader<R> where R: Read. Он принимает объект реализующий трейт Read (то есть у объекта есть метод read), употребляет его "насовсем", возвращает BufReader, то есть, в терминах STL/C++ это буферизованный istream. inner был небуферизованным, а BufReader -- буферизованный. Я вижу такое, и я понимаю, что мне не удастся попеременно читать буферизовано или небуферизовано, потому что BufReader хочет владеть низлежащим небуферизованным потоком. Зачем ему это надо, я не уверен навскидку, у меня есть разные предположения, но это другой вопрос.

А ещё у BufReader'а есть метод into_inner(self) -> R, который принимает self "по значению", то есть он употребляется насовсем, и возвращает R по значению, то есть совмещая это с конструктором, мы понимаем, что владением исходным небуферизованным istream'ом можно получить обратно, только BufReader этого не переживёт.

Ни в одном из этих примеров для объектов нет смысла передаваться по-дефолту созданием копии, потому что даже если бы это и было бы возможно, всё равно никто не пользовался бы этой возможностью. Иногда, может быть, в каких-то специальных условиях это имеет смысл, но на тот случай есть clone. А вот передавать владение объектом имеет глубинный смысл, как с точки зрения использования моментально детектируемого паттерна, так и с точки обеспечения безопасности. String сделанный на повторном использовании памяти с Vec появится только тогда, когда Vec прекратит существовать. А это значит, что не будет двух разных указателей на буфер в памяти хранящий char'ы, а значит не случится, например, такого, что один указатель падёт жертвой drop и освободит память, а другой указатель затем будет разадресован.

>>> в C/C++ невозможно передать владение
> Можно, см. std::move().

Нну, это костыль. Что-то типа того, но не то. std::move, как бэ, перемещает объект, но хех, что будет если ты сделаешь:

string s = "хеллоувролд";
foo(std::move(s));
std::cout << s << std::endl;

Будет UB. Я чесслово не знаю, что произойдёт, в силу всех этих оптимизаций, проводимых llvm и gcc, но при том уровне оптимизации который был в 90-х это бы означало, что s нихрена не переместился и остался доступен здесь.

Запрет на такие штуки на уровне ошибок компиляции очень важен, это гарантия того, что как в декларациях API написано его использовать, именно так его и будут использовать. Такие штуки могут приводить к нелокальным багам, после которых бывает очень сложно найти виноватого в рантайм-ошибке. А это значит, что от писателя вызывающего кода надо ждать очень точного понимания тех API, которые он дёргает. А это значит, что а) C++ программисты не любят внешние API, потому что их надо скрупулёзно изучать, прежде чем использовать, проще навелосипедить своё; б) писатели внешних API вынуждены думать, как бы так не разложить граблей, которые сложно заметить.

>>> В-третьих, дефолты меняются.
> В С++ тоже можно менять поведение класса при создании копии его объекта.
> Выполняется это переопределением конструктора копирования (и других методов, там где надо).
> Если очень нужна эффективность по-дефолту, чаще всего реализуют CopyOnWrite.

Когда я последний раз щупал эти конструкторы, они сделаны таким образом, что на них невозможно реализовывать некоторые паттерны безопасно. То есть, ты делаешь что-то эдакое, но чтобы оно работало, надо чтобы вызывающий код понимал как и что надо делать, чтобы не наступить на грабли. А это значит, что внутри реализации класса нет уверенности в том, на что можно положиться. Можно ли положиться, что вот этот вот inner istream внутри BufReader -- это владение BufReader? Когда выполняется BufReader::drop, следует ли вызвать drop на inner? Если inner принадлежит BufReader'у, то да, это _необходимо_, потому что иначе объект inner просто "утечёт": либо останется потерянный открытый файловый дескриптор, либо потерянный выделенный кусок кучи, либо и то и другое, и может быть ещё что-то третье. Если же вызывающий код сохранил у себя ссылку на inner, то такой drop приведёт к последующему use-after-free в вызывающем коде. Ну или если inner -- это просто файловый дескриптор, то вызывающий код либо столкнётся с внезапно ставшим невалидным файловым дескриптором, или, что хуже, будет читать из нового файлового дескриптора, который был открыт между drop и попыткой чтения и занял то же значение, что и дропнутый файловый дескриптор. А такое уже вообще хрен отладишь, потому что такая ошибка может вылезать в коде, который вообще никак логически не связан ни с данным инстансом BufReader'а, ни с кодом работающим с BufReader'ом. В смысле, глядя на место возникновения ошибки можно математически доказать, что там всё правильно написано и без ошибок, и это даже будет так, но ошибка всё равно будет возникать, и где тот код, который нacpaл? Как его найти теперь?

 

Ваше сообщение
Имя*:
EMail:
Для отправки ответов на email укажите знак ! перед адресом, например, !user@host.ru (!! - не показывать email).
Более тонкая настройка отправки ответов производится в профиле зарегистрированного участника форума.
Заголовок*:
Сообщение*:
 
При общении не допускается: неуважительное отношение к собеседнику, хамство, унизительное обращение, ненормативная лексика, переход на личности, агрессивное поведение, обесценивание собеседника, провоцирование флейма голословными и заведомо ложными заявлениями. Не отвечайте на сообщения, явно нарушающие правила - удаляются не только сами нарушения, но и все ответы на них. Лог модерирования.



Партнёры:
PostgresPro
Inferno Solutions
Hosting by Hoster.ru
Хостинг:

Закладки на сайте
Проследить за страницей
Created 1996-2024 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру