Побочные эффекты создания явно неиспользуемого объекта

Скажем, у меня есть структура, которая предоставляет метод, который выполняет некоторую работу (имеет полезные побочные эффекты), а затем возвращает свой экземпляр. Должен ли я каким-то образом использовать этот экземпляр, чтобы гарантировать выполнение кода, имеющего побочные эффекты?

Согласно стандарту C++, RVO и NRVO могут исключить правило «как будто». Однако я ожидал бы от Rust другого поведения, особенно при использовании идиомы _ = .... Теперь я задаюсь вопросом, ошибаюсь ли я, поступая так.

Чтобы привести конкретный пример, давайте посмотрим на esp_hal::gpio::Io::new(), который вызывает Self::new_with_priority() и перед возвратом своего экземпляра настраивает io-драйвер как побочный эффект. Могу ли я затем использовать функцию из-за ее побочных эффектов, но явно отбросить возвращаемый объект, например:

let _ = esp_hal::gpio::Io::new();

В отличие от C++, я ожидаю, что компилятор не устранит побочные эффекты. Более того, я ожидаю, что это исключит создание экземпляра Io. Фактически, именно так я бы интерпретировал семантику/намерение идиомы _ = .... Должен ли я обновить свои убеждения?

Это зависит от того, что вы подразумеваете под побочными эффектами, но, скорее всего, единственный побочный эффект, который можно удалить ржавчиной, — это выделения. Любые другие вызовы внешних функций и нестабильные операции сохраняются.

drewtato 19.07.2024 07:23

«особенно при использовании идиомы _ = ...», на самом деле это, скорее всего, упускает все это, чем нет: _ = - довольно неожиданный уголок языка в том смысле, что он фактически не работает, например, если RHS это !Copy объект *он не будет перемещен`: play.rust-lang.org/… однако, как и drawtato, я не думаю, что семантика языка позволит избежать побочных эффектов, даже если вы игнорируете возвращаемое значение. Хотя я не знаю, как компилятор взаимодействует, например. как м! блоки.

Masklinn 19.07.2024 08:09

Обратите внимание, что в C++ разрешено опускать конструкцию статического объекта, но это не то, что вы делаете в своем примере на Rust; компилятору C++ также не разрешено опускать эту конструкцию.

cafce25 19.07.2024 09:36

Мне остается неясным, разрешено ли Rust исключать побочные вызовы Clone::clone для Copy типов (см. github.com/rust-lang/rfcs/pull/3197 для соответствующего обсуждения).

eggyal 19.07.2024 09:43

@cafce25 Компиляторам C++ также не разрешается отбрасывать побочные эффекты инициализации объектов длительности статического хранения. В зависимости от определенных критериев может случиться, что инициализация задерживается до первого (odr-) использования переменной (косвенно) из main (для поддержки динамических библиотек). Однако существуют общие параметры компоновщика и другие, которые делают поведение несоответствующим, и распространенной ошибкой является забвение использования odr. Составители также склонны не учитывать использование odr, которое правильно оптимизировано.

user17732522 19.07.2024 09:59

«Согласно стандарту C++, RVO и NRVO могут исключить правило «как если бы».: В соответствии с этими правилами не допускается исключение исходной конструкции объекта, а только копирование/перемещение конструкции из исходной (временной) объект в целевой объект. Кроме того, поскольку C++17 RVO как таковой больше не существует. Вместо этого языковые правила были изменены таким образом, что поведение RVO всегда было обязательным.

user17732522 19.07.2024 10:10

@user17732522 user17732522 Да, я хотел сказать больше о том, что C++ не позволяет удалять побочный эффект и в C++-эквиваленте опубликованного кода Rust.

cafce25 19.07.2024 12:01

@eggyal Спасибо за Clone::clone за ссылку на Copy типы, очень интересно!

gstukelj 19.07.2024 18:07
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
1
8
86
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Важно отметить, что деструктурировать можно с помощью let. В более общем смысле, вы можете сопоставить шаблон с let, если шаблон неопровержим.

_ — это узор, который означает «сочетать что угодно и не связывать». Другими словами, все, что соответствует _ в шаблоне, будет удалено.

Итак, из этого мы можем сделать вывод, что let _ = anything(); почти1 семантически идентично во всех отношениях просто anything();. При использовании let _ значение, возвращаемое из anything(), отбрасывается из-за поведения, описанного выше. Если не использовать let _, значение, возвращаемое из anything(), удаляется в конце инструкции, поскольку это неиспользуемое временное значение.

При этом не имеет большого значения, скажете ли вы let _ = function(); или просто function();. Что касается того, что происходит с возвращаемым значением функции, они делают одно и то же в один и тот же момент времени.

Кроме того, в Rust нет специального понятия конструкторов, как в C++. Функции, создающие значения, можно назвать «функциями-конструкторами», но это всего лишь терминология; на уровне языка нет концептуальной разницы между функцией, создающей Self, и любой другой функцией.

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

Подводя итог, можно сказать, что компилятор не собирается оптимизировать этот вызов функции, предполагая, что он имеет наблюдаемые побочные эффекты2. Это верно независимо от того, используете ли вы синтаксис let _.


1 «Почти» означает, что let _ = y; считается использованием y, а y сам по себе нет. Обычно это используется для игнорирования Result, возвращаемых функцией, когда не важно, завершилась ли операция неудачно. let _ = fallible_operation(); будет подавлять предупреждение «неиспользуемый Result, который необходимо использовать».

2 Любопытно, что Rust не считает выделение побочным эффектом, и поэтому в некоторых случаях выделения можно оптимизировать.

К сноске 2: можно оптимизировать не только распределение, но и перераспределение (т. е. выделение, перемещение и освобождение); и я также отсылаю вас к моему комментарию выше о Clone::clone типах Copy.

eggyal 19.07.2024 09:58

@eggyal Верно, это не должен был быть исчерпывающий список исключений, просто это мне показалось интересным.

cdhowie 19.07.2024 09:59

Придирка: оптимизатор мог бы также решить, что любой вызов функции может быть встроен, и исключить сам вызов функции, но он не может удалить эффективные операции.

cafce25 19.07.2024 13:13

@cdhowie Это вторая сноска, которая на самом деле отвечает на мой вопрос. (Также обратите внимание, что я никогда не использовал слово «конструктор» в своем вопросе :))

gstukelj 19.07.2024 17:59

@cafce25 «Вызов функции» здесь означает не «сборку, которая вызывает невстроенную функцию», а «эффекты вызова функции».

cdhowie 19.07.2024 20:44

Другие вопросы по теме