Это правила, которые я лично использую (по порядку).
Передача по значению (T
), если у параметра есть универсальный тип и признак (и), которые этотвсе реализации универсального типа берут &self
или &mut self
, но есть общее impl
для &T
или &mut T
(соответственно) для всех типов T
, которые реализуют эту черту (или этичерты).Например, в std::io::Write
все методы принимают &mut self
, но в стандартной библиотеке предусмотрен общий импл impl<'a, W: Write + ?Sized> Write for &'a mut W
.Это означает, что, хотя вы принимаете T
(где T: Write
) по значению, можно передать &mut T
, поскольку &mut T
также реализует Write
.
Передачазначение (T
), если вы должны стать владельцем значения (например, потому что вы передаете его другой функции / методу, который принимает его по значению, или потому что альтернативы потребуют потенциально дорогих клонов).
Передайте по изменяемой ссылке (&mut T
), если вам нужно видоизменить объект (вызывая другие функции / методы, которые принимают объект по изменяемой ссылке, или просто перезаписывая его, и вы хотите, чтобы вызывающая сторона увидела новоезначение), но не нужно вступать во владение им.
Передача по значению (T
), если тип Copy
и мал (мой критерий для малого size_of::<T>() <= size_of::<usize>() * 2
, но у других людей могут быть немного другие критерии).Примитивные целочисленные типы и типы с плавающей точкой являются примерами таких типов.Передача значений этих типов по ссылке создаст ненужную косвенность в памяти, поэтому вызывающий должен будет выполнить дополнительную машинную инструкцию, чтобы прочитать ее.Когда size_of::<T>() <= size_of::<usize>()
, вы обычно ничего не сохраняете, передавая значение по ссылке, потому что T
и &T
обычно передаются в одном регистре (если у функции достаточно мало параметров).
Передача по общей ссылке (&T
) в противном случае.
В общем, предпочитайте передачу по общей ссылке, когда это возможно.Это позволяет избежать потенциально дорогих клонов, когда тип является большим или управляет ресурсами, отличными от памяти, и дает наибольшую гибкость вызывающей стороне в том, как значение может использоваться после вызова.
Например, является ли этохорошая идея пройти мимо T
, если я хочу освободить T
после того, как он выходит из области видимости в моей дочерней функции (функция, которую я передаю T
)
Вы былучше иметь вескую причину для этого!Если вы когда-нибудь решите, что вам действительно нужно использовать T
позже в вызывающей программе, вам придется изменить подпись вызывающей стороны и обновить все сайты вызовов (потому что в отличие от C ++, где переход от T
до const T&
в основном прозрачный, переход от T
к &T
в Rust отсутствует: вы должны добавить &
перед аргументом во всех сайтах вызовов).
Я рекомендую использовать Clippy если вы еще не используете его. В Clippy есть сообщение, которое может уведомить вас, если вы напишите функцию, которая принимает аргумент по значению, но функция не должна вступать во владение ею (по умолчанию это предупреждение используется, но больше не используется.?, поэтому вы должны включить его вручную с помощью #[warn(clippy::needless_pass_by_value)]
).