Можно ли сделать шрифт только подвижным, а не копируемым?

96

Примечание редактора : этот вопрос задавался до Rust 1.0, и некоторые утверждения в вопросе не обязательно верны в Rust 1.0. Некоторые ответы были обновлены с учетом обеих версий.

У меня есть эта структура

struct Triplet {
    one: i32,
    two: i32,
    three: i32,
}

Если я передаю это функции, она неявно копируется. Иногда я читаю, что некоторые значения не копируются и поэтому их нужно переместить.

Можно ли сделать эту структуру Tripletне копируемой? Например, можно ли реализовать Tripletтрейт, который сделает не копируемым и, следовательно, «перемещаемым»?

Я где-то читал, что нужно реализовать Cloneчерту, чтобы копировать вещи, которые не могут быть неявно копируемыми, но я никогда не читал об обратном, то есть о том, что есть что-то, что неявно копируется, и делает его некопируемым, чтобы вместо этого он перемещался.

Есть ли в этом хоть какой-то смысл?

Кристоф
источник
1
paulkoerbitz.de/posts/… . Здесь есть хорошие объяснения того, почему перемещение по сравнению с копированием.
Шон Перри

Ответы:

165

Предисловие : этот ответ был написан до того, как были реализованы встроенные характеристики, в частности, аспекты . Я использовал блочные кавычки, чтобы указать разделы, которые применимы только к старой схеме (той, которая применялась, когда был задан вопрос).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. Это было бы прекрасно, если бы мы сделали семантическую копию vinto 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.

гуон
источник
@dbaupp: А вы случайно не знаете, в какой версии Rust появились встроенные черты? Я бы подумал 0,10.
Matthieu M.
@MatthieuM. он еще не реализован, и на самом деле недавно были предложены некоторые изменения в конструкции встроенных модулей выбора .
huon
Я думаю, что эту старую цитату следует стереть.
Stargateur
1
# [derive (Copy, Clone)] следует использовать на Triplet not impl
shadowbq
6

Самый простой способ - встроить в свой шрифт что-то, что нельзя копировать.

Стандартная библиотека предоставляет «тип маркера» именно для этого варианта использования: NoCopy . Например:

struct Triplet {
    one: i32,
    two: i32,
    three: i32,
    nocopy: NoCopy,
}
BurntSushi5
источник
15
Это не действует для Rust> = 1.0.
malbarbo