Предисловие : этот ответ был написан до того, как были реализованы встроенные характеристики, в частности, аспекты . Я использовал блочные кавычки, чтобы указать разделы, которые применимы только к старой схеме (той, которая применялась, когда был задан вопрос).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
тогда было бы два вектора, указывающие на одно и то же выделение, оба с деструкторами, которые его освобождают ... вызывая двойное освобождение , что является проблемой. NB. Это было бы прекрасно, если бы мы сделали семантическую копию v
into 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
. Однако это изменится, когда будут реализованы встроенные характеристики .
Как упоминалось выше, реализованы встроенные признаки opt-in, поэтому у компилятора больше нет автоматического поведения. Однако в прошлом для автоматического поведения использовались те же правила, что и для проверки законности реализации Copy
.