Почему свойство Copy необходимо для инициализации массива по умолчанию (struct valueed)?

Когда я определяю такую ​​структуру, я могу передать ее функции по значению, не добавляя ничего конкретного:

#[derive(Debug)]
struct MyType {
    member: u16,
}

fn my_function(param: MyType) {
    println!("param.member: {}", param.member);
}

Когда я хочу создать массив MyType экземпляры со значением по умолчанию

fn main() {
    let array = [MyType { member: 1234 }; 100];
    println!("array[42].member: ", array[42].member);
}

Компилятор Rust говорит мне:

error[E0277]: the trait bound `MyType: std::marker::Copy` is not satisfied
  --> src/main.rs:11:17
   |
11 |     let array = [MyType { member: 1234 }; 100];
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::marker::Copy` is not implemented for `MyType`
   |
   = note: the `Copy` trait is required because the repeated element will be copied

Когда я реализую Copy а также Clone, все работает:

impl Copy for MyType {}
impl Clone for MyType {
    fn clone(&self) -> Self {
        MyType {
            member: self.member.clone(),
        }
    }
}
  1. Почему мне нужно указать пустой Copy черта реализации?

  2. Есть ли более простой способ сделать это, или я должен что-то переосмыслить?

  3. Почему это работает при передаче экземпляра MyType к функции по значению? Я предполагаю, что он перемещается, поэтому в первую очередь его нет.

2 ответа

Решение

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

По умолчанию каждый тип является только подвижным (не копируемым). Это означает, что значения таких типов перемещаются:

let x = SomeNonCopyableType::new();
let y = x;
x.do_something();      // error!
do_something_else(x);  // error!

Вы видите, значение, которое было сохранено в x был перемещен в yи поэтому вы ничего не можете сделать с x,

Семантика Move является очень важной частью концепции владения в Rust. Вы можете прочитать больше об этом в официальном руководстве.

Некоторые типы, однако, достаточно просты, поэтому их побайтная копия также является их семантической копией: если вы копируете побайтовое значение, вы получите новое полностью независимое значение. Например, примитивные числа являются такими типами. Такое свойство обозначается Copy черта в Rust, т.е. если тип реализует Copy, тогда значения этого типа неявно копируются. Copy не содержит методов; он существует исключительно для того, чтобы отметить, что реализующие типы имеют определенное свойство, и поэтому его обычно называют признаком маркера (как и несколько других признаков, которые делают подобные вещи).

Тем не менее, это не работает для всех типов. Например, структуры, подобные динамически размещенным векторам, не могут быть автоматически скопированы: если бы они были, адрес содержащегося в них выделения также был бы скопирован байтами, и тогда деструктор такого вектора будет запущен дважды по одному и тому же распределению, вызывая это указатель освобождается дважды, что является ошибкой памяти.

Поэтому по умолчанию пользовательские типы в Rust не копируются. Но вы можете подписаться на это, используя #[derive(Copy, Clone)] (или, как вы заметили, используя прямой impl; они эквивалентны, но derive обычно лучше читает)

#[derive(Copy, Clone)]
struct MyType {
    member: u16
}

(вывод Clone необходимо, потому что Copy наследуется Cloneтак что все что есть Copy также должен быть Clone)

Если ваш тип может быть автоматически копируемым в принципе, то есть он не имеет связанного деструктора и все его члены Copyзатем с derive Ваш тип также будет Copy,

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

Выше был ответ на 1 и 2. Что касается 3, да, вы абсолютно правы. Это работает именно потому, что значение перемещено в функцию. Если вы пытались использовать переменную MyType введите после того, как вы передадите его в функцию, вы быстро заметите ошибку при использовании перемещенного значения.

Зачем мне нужно указывать пустую реализацию признака копирования?

Copy это особая встроенная черта, такая, что T реализации Copy представляет, что безопасно дублировать значение типа T с мелкой байтовой копией.

Это простое определение означает, что нужно просто сообщить компилятору, что семантика верна, поскольку в поведении во время выполнения нет фундаментальных изменений: оба - движение (неCopy type) и "copy" - это мелкие байтовые копии, вопрос лишь в том, можно ли использовать источник позже. Смотрите более старый ответ для более подробной информации.

(Компилятор будет жаловаться, если содержимое MyType не Copy сам; ранее это было бы автоматически реализовано, но все изменилось с помощью встроенных черт.)

Создание массива дублирует значение с помощью мелких копий, и это гарантированно безопасно, если T является Copy, Это безопасно в более общих ситуациях, # 5244 покрывает некоторые из них, но в основном, неCopy struct не сможет использоваться для автоматического создания массива фиксированной длины, потому что компилятор не может сказать, что дублирование является безопасным / правильным.

Есть ли более простой способ сделать это, или я должен что-то переосмыслить (я из C)?

#[derive(Copy)]
struct MyType {
    member: u16
}

вставит соответствующую пустую реализацию (#[derive] работает с несколькими другими чертами, например, часто видит #[derive(Copy, Clone, PartialEq, Eq)].)

Почему это работает при передаче экземпляра MyType к функции по значению? Я предполагаю, что он перемещается, поэтому в первую очередь его нет.

Ну, без вызова функции нельзя увидеть поведение перемещения или копирования (если бы вы вызывали ее дважды, неCopy значение, компилятор выдаст ошибку о перемещенных значениях). Но "перемещение" и "копирование" на машине одинаковы. Все значения значения по значению являются копиями семантически в Rust, как и в C.

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