Ответ 1
Предисловие: этот ответ был написан до встроенных признаков, особенно Copy
аспектов были реализованы. Я использовал кавычки блока, чтобы указать разделы, которые применяются только к старой схеме (той, которая применялась при задании вопроса).
Старый. Чтобы ответить на основной вопрос, вы можете добавить поле маркера, сохраняющее значение
NoCopy
. Например.struct Triplet { one: int, two: int, three: int, _marker: NoCopy }
Вы также можете сделать это, используя деструктор (используя
Drop
trait), но использование типов маркеров является предпочтительным, если деструктор ничего не делает.
Теперь типы теперь перемещаются по умолчанию, то есть когда вы определяете новый тип, он не реализует 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
изнутри a&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
, тогда было бы два вектора, указывающих на одно и то же распределение, как с деструкторами, освобождающими его... вызывая a двойной бесплатный, что является проблемой. NB. Это было бы прекрасно, если бы мы сделали семантическую копию v
в w
, так как тогда w
будет ее собственным независимым Vec<u8>
, а деструкторы не будут топтаться друг от друга.
Здесь есть несколько возможных исправлений:
- Пусть программист справится с этим, как и C. (там нет деструкторов в C, так что это не так плохо... вы просто останетесь с утечками памяти.: P)
- Выполнять семантическую копию неявно, так что
w
имеет свое собственное выделение, например С++ с его конструкторами копирования. - Regard by-value использует в качестве передачи права собственности, так что
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
.