유형을 이동 만 가능하고 복사 불가능하게 만들 수 있습니까?


96

편집자 주 :이 질문은 Rust 1.0 이전에 제기되었으며이 질문의 일부 주장은 Rust 1.0에서 반드시 사실이 아닙니다. 두 버전을 모두 해결하기 위해 일부 답변이 업데이트되었습니다.

이 구조체가 있습니다

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

이것을 함수에 전달하면 암시 적으로 복사됩니다. 이제 일부 값은 복사 할 수 없으므로 이동해야한다는 것을 읽었습니다.

이 구조체를 Triplet복사 불가능하게 만들 수 있습니까? 예를 들어, Triplet복사 불가능하고 따라서 "이동 가능" 하게 만드는 특성을 구현할 수 있습니까?

나는 Clone암시 적으로 복사 할 수없는 것을 복사 하기 위해 특성 을 구현 해야하는 어딘가에서 읽었 지만, 그 반대의 방법에 대해서는 결코 읽지 않았습니다.

그게 말이 되나?


1
paulkoerbitz.de/posts/... . 이동 대 복사 이유에 대한 좋은 설명입니다 .
Sean Perry

답변:


165

서문 :이 답변은 이전에 기록 된 선택 하에서 내장 특성 -specifically 측면이 구현 -were. 이전 계획 (질문을 받았을 때 적용한 계획)에만 적용되는 섹션을 나타 내기 위해 블록 따옴표를 사용했습니다.Copy


기존 : 기본적인 질문에 답하기 위해 NoCopy값을 저장하는 마커 필드를 추가 할 수 있습니다 . 예

struct Triplet {
    one: int,
    two: int,
    three: int,
    _marker: NoCopy
}

소멸자를 사용하여 ( Droptrait 구현을 통해) 할 수도 있지만, 소멸자가 아무것도하지 않는 경우 마커 유형을 사용하는 것이 좋습니다.

이제 유형은 기본적으로 이동합니다. 즉, 새 유형을 정의 할 때 유형에 Copy대해 명시 적으로 구현 하지 않는 한 구현 되지 않습니다 .

struct Triplet {
    one: i32,
    two: i32,
    three: i32
}
impl Copy for Triplet {} // add this for copy, leave it out for move

구현은 모든 유형이 new struct또는 enum그 자체 인 경우에만 존재할 수 있습니다 Copy. 그렇지 않은 경우 컴파일러는 오류 메시지를 인쇄합니다. 또한 유형 에 구현 이없는 경우에만 존재할 수 있습니다 Drop.


당신이 묻지 않은 질문에 대답하기 위해 ... "무브와 카피는 어떻게 된거 야?":

먼저 두 가지 다른 "사본"을 정의하겠습니다.

  • 바이트 사본 이있는 경우 만 얕게, 다음하지 포인터를 객체 바이트 별을 복사하는 예입니다, (&usize, u64)그것은 64 비트 컴퓨터에서 16 바이트이며, 얕은 복사는 그 16 바이트를 복용하고 자신을 복제 할 것 메모리의 다른 16 바이트 청크에서 값 없이 터치 usize의 타단 &. 즉,을 호출하는 것과 같습니다 memcpy.
  • 의미 사본 값을 복제는 안전하게 이전에 개별적으로 사용할 수있는 새 (다소) 독립적 인 인스턴스를 만들 수 있습니다. 예를 들어의 의미 론적 사본은 Rc<T>참조 횟수를 늘리는 것과 관련되고,의 의미 론적 사본은 Vec<T>새로운 할당을 생성 한 다음 각 저장된 요소를 이전 항목에서 새 항목 으로 의미 론적으로 복사하는 것을 포함합니다. 이러한 일 수 전체 복사본 (예 Vec<T>) 또는 얕은 (예 Rc<T>닿지 않도록 저장 T), Clone작업의 최소 량을 의미 타입의 값을 복사 할 필요 느슨하게 정의 T내부 &TT.

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. 그러나 옵트 인 내장 특성 이 구현 되면 변경됩니다 .

위에서 언급했듯이 옵트 인 내장 트레이 트가 구현되므로 컴파일러는 더 이상 자동 동작을 갖지 않습니다. 그러나 과거에 자동 동작에 사용 된 규칙은를 구현하는 것이 합법적인지 확인하는 규칙과 동일합니다 Copy.


@dbaupp : 어떤 버전의 Rust에서 옵트 인 내장 특성이 나타 났는지 알고 계십니까? 0.10이라고 생각합니다.
Matthieu M.

@MatthieuM. 아직 구현되지 않았으며 실제로 최근 에 옵트 인 내장 디자인 에 대해 몇 가지 제안 된 수정 사항이 있습니다 .
huon

오래된 인용문은 지워야한다고 생각합니다.
Stargateur

1
# [파생 (복사, 복제)]을하지 IMPL 삼중에서 사용할 수 있어야
shadowbq

6

가장 쉬운 방법은 복사 할 수없는 유형을 포함하는 것입니다.

표준 라이브러리는 정확히이 사용 사례에 대한 "마커 유형"을 제공합니다 : NoCopy . 예를 들면 :

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

15
이것은 Rust> = 1.0에는 유효하지 않습니다.
malbarbo
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.