Можно ли сделать тип только подвижным, а не копируемым?
Примечание редактора: этот вопрос задавался до Rust 1.0, и некоторые из утверждений в вопросе не обязательно верны в Rust 1.0. Некоторые ответы были обновлены с учетом обеих версий.
У меня есть эта структура
struct Triplet {
one: i32,
two: i32,
three: i32,
}
Если я передаю это функции, она неявно копируется. Теперь иногда я читаю, что некоторые значения не могут быть скопированы и поэтому должны быть перемещены.
Будет ли возможно сделать эту структуру Triplet
без копируемого? Например, было бы возможно реализовать черту, которая сделала бы Triplet
не копируемый и, следовательно, "подвижный"?
Я где-то читал, что нужно реализовать Clone
свойство копировать то, что не является неявно копируемым, но я никогда не читал об обратном, то есть о чем-то, что неявно копируемо, и о том, что оно не подлежит копированию, и поэтому оно перемещается.
Имеет ли это хоть какой-то смысл?
2 ответа
Предисловие: Этот ответ был написан до включения встроенных признаков - особенно 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)
на 64-битном компьютере это 16 байтов, и поверхностная копия будет брать эти 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
в 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
marker отменяет автоматическое поведение компилятора, предполагая, что типы, которые могут бытьCopy
(т.е. только содержащие совокупности примитивов и&
) являютсяCopy
, Однако это изменится, когда будут реализованы дополнительные встроенные черты.
Как упоминалось выше, встроенные свойства opt-in реализованы, поэтому компилятор больше не работает автоматически. Тем не менее, правило, используемое для автоматического поведения в прошлом, это те же правила для проверки законности реализации. Copy
,