Передмова : Цей відповідь була написана до неавтоматичного вбудованих ознак -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
.