Я прохожу https://rust-exercisions.com/ и в одном из упражнений вы реализовали тип Saturating16. В частности, один из тестов требует умения составить Saturating16 из &u8.
Вот реализация, которую я использовал:
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct SaturatingU16 {
value: u16,
}
impl From<&u8> for SaturatingU16 {
fn from(value: &u8) -> Self {
SaturatingU16 {
value: (*value).into(),
}
}
}
Меня интересует вот что: value: (*value).into().
Здесь я предполагаю, что разыменование *value неявно скопирует базовый &u8 в u8, а затем .into() создаст новый u16. Во-первых, верно ли это предположение?
Во-вторых, если все правильно, похоже, что разыменование — это накладные расходы, которые нам не нужны — мы создаем два значения и сразу же выбрасываем одно из них. Есть ли способ перейти напрямую из &u8 в u16?

У вас есть самый быстрый способ.
Ссылка — это тип указателя, поэтому в какой-то момент должно произойти разыменование (то есть загрузка), чтобы получить указанное значение. Этот шаг не является «накладными».
Забота о получении u8 и только затем преобразовании его в u16 имеет смысл, но при прохождении оптимизации и том, как работают регистры в процессорах, различие между u8 и u16 в значительной степени стирается. Регистры всегда имеют ширину адреса (или больше), поэтому загрузка u8 в регистр автоматически означает, что его можно использовать как u16 - при условии, что старшие биты очищены.
Генерация сборки x86 от &u8 до u16 дает следующее:
#[no_mangle]
pub fn convert(num: &u8) -> u16 {
(*num).into()
}
convert:
movzbl (%rdi), %eax
retq
Это преобразование можно было выполнить с помощью одной инструкции (комбинация загрузки байтов и расширения нулями).
Об этом позаботится оптимизация компилятора, и вам не о чем беспокоиться, особенно если вы не принимаете решения, основанные на дизассемблере.