Передмова : Цей відповідь була написана до неавтоматичного вбудованих ознак -specifically на Copyаспектах ставилися реалізовані. Я використовував блок-лапки, щоб вказати розділи, які застосовувались лише до старої схеми (тієї, що застосовувалася, коли було задано питання).
Старий : Щоб відповісти на основне запитання, можна додати поле маркера, що зберігає NoCopyзначення . Напр
struct Triplet {
one: int,
two: int,
three: int,
_marker: NoCopy
}
Ви також можете зробити це, використовуючи деструктор (за допомогою реалізації Dropознаки ), але використання типів маркерів є кращим, якщо деструктор нічого не робить.
Типи тепер переміщуються за замовчуванням, тобто коли ви визначаєте новий тип, який він не реалізує, Copyякщо ви явно не реалізуєте його для свого типу:
struct Triplet {
one: i32,
two: i32,
three: i32
}
impl Copy for Triplet {} // add this for copy, leave it out for move
Реалізація може існувати лише за умови, що кожен тип, що міститься в новому, structабо enumє самим собою Copy. Якщо ні, компілятор надрукує повідомлення про помилку. Він також може існувати, лише якщо тип не має Dropреалізації.
Щоб відповісти на запитання, яке ви не задавали ... "що з ходами та копію?":
Спочатку я визначу дві різні "копії":
- байт копія , яка просто неглибоко копіювання об'єкта байт в байт, а не слід покажчики, наприклад , якщо у вас є
(&usize, u64), це 16 байт на 64-розрядному комп'ютері, і неповну копію буде приймати ці 16 байт і тиражування їх значення в якомусь іншому 16-байтовому фрагменті пам'яті, не торкаючись значка usizeна іншому кінці &. Тобто це еквівалентно дзвінку memcpy.
- семантична копія , дублюючи значення для створення нового (кілька) незалежного примірника , який можна безпечно використовувати окремо для старої. Наприклад, семантична копія
Rc<T>включає просто збільшення кількості посилань, а семантична копія Vec<T>включає створення нового розподілу, а потім семантичну копію кожного збереженого елемента зі старого в новий. Це можуть бути глибокі копії (наприклад Vec<T>) або неглибокі (наприклад Rc<T>, не стосуються збережених T), Cloneвільно визначається як найменший обсяг роботи, необхідний для семантичного копіювання значення типу Tзсередини &Tдо T.
Rust - це як C, кожне використання значення за значенням - це байтова копія:
let x: T = ...;
let y: T = x; // byte copy
fn foo(z: T) -> T {
return z // byte copy
}
foo(y) // byte copy
Вони являють собою байтові копії, незалежно від Tпереміщення чи ні, або "неявно копіюються". (Щоб бути зрозумілим, вони не обов’язково буквально побайтові копії під час виконання: компілятор може вільно оптимізувати копії, якщо поведінка коду збережена.)
Однак є основна проблема з байтовими копіями: ви отримуєте дубльовані значення в пам'яті, що може бути дуже погано, якщо вони мають деструктори, наприклад
{
let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
} // destructors run here
Якби це wбула просто байтова копія, vтоді було б два вектори, що вказували б на одне і те ж розподіл, обидва з деструкторами, які його звільняють ... викликаючи подвійний вільний , що є проблемою. Примітка. Це було б чудово, якби ми зробили семантичну копію vв w, оскільки тоді це wбуло б самостійно, Vec<u8>і деструктори не топтали б один одного.
Тут є кілька можливих виправлень:
- Нехай програміст обробляє це, як C. (у C немає деструкторів, тому це не так погано ... замість цього ви просто залишаєтесь із витоками пам'яті.: P)
- Виконайте семантичну копію неявно, так що
wвона має власне виділення, як C ++ з її конструкторами копій.
- Вважайте використання за вартістю як передачу права власності, так що воно
vбільше не може використовуватися і не запускається його деструктор.
Останнє - те, що робить Rust: переміщення - це просто використання за значенням, де джерело статично недійсним, тому компілятор запобігає подальшому використанню недійсної пам’яті.
let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
println!("{}", v); // error: use of moved value
Типи, які мають деструктори, повинні переміщатися, коли використовується побічне значення (інакше, коли копіюється байт), оскільки вони мають управління / право власності на деякий ресурс (наприклад, виділення пам'яті або дескриптор файлу), і дуже малоймовірно, що байтова копія буде правильно дублювати це право власності.
"Ну ... що таке неявна копія?"
Подумайте про примітивний тип, наприклад u8: байтова копія проста, просто скопіюйте один байт, а семантична копія так само проста, скопіюйте єдиний байт. Зокрема, байтова копія - це семантична копія ... Rust навіть має вбудовану ознаку,Copy яка фіксує, які типи мають однакові семантичні та байтові копії.
Отже, для цих Copyтипів використання за значенням також є автоматично семантичними копіями, і тому цілком безпечно продовжувати використовувати джерело.
let v: u8 = 1;
let w: u8 = v;
println!("{}", v); // perfectly fine
Старий : NoCopyмаркер замінює автоматичну поведінку компілятора, припускаючи, що типами, які можуть бути Copy(тобто містять лише сукупності примітивів та &), є Copy. Однак це зміниться, коли буде застосовано вбудовані риси .
Як уже згадувалося вище, вбудовані властивості, що вмикаються, реалізовані, тому компілятор більше не має автоматичної поведінки. Однак правило, яке використовувалось для автоматичної поведінки в минулому, є однаковими правилами перевірки законності впровадження Copy.