Copy
Copy
트레이트가 구현되어 있으면 러스트는 묵시적으로 복제체를 생성한다. 값의 소유권이 이동된 후에 사용하더라도 복제체가 사용되었으므로 컴파일 에러가 발생하지 않는다.
그렇다고 자동 복제(automatic cloning)이라고 할 수는 없는데, 몇 가지 제약이 있기 때문이다.
- Copy는 Clone의 서브 트레이트이므로 Clone 역시 구현되어야 한다.
- 타입이 메모리에서 차지하는 std::mem::size_of 바이트 이외의 추가 리소스(예: 힙 메모리, 파일 핸들 등)를 관리하지 않아야 한다.
- 타입이 가변 참조여서는 안된다.
이러한 제약 때문에 String
은 Copy
를 구현하지 않는다. Copy
는 비트 단위(bitwise)로 데이터를 복제한다. String
을 비트 단위로 복제하게 된다면, 원본과 복제본은 동일한 포인터를 통해 동일한 대상을 가리키게 된다. 이러한 구현은 안전하지 않다.
가변 참조는 단 하나만 존재할 수 있는데, 마찬가지로 비트 단위로 복제하게 되면 가변 참조가 여러 개 존재하게 되므로 안전하지 않다.
문제
// TODO: 테스트를 컴파일하고 통과할 수있게 필요한 트레이트를 구현하세요.
// 테스트를 수정해서는 안 됩니다.
pub struct WrappingU32 {
value: u32,
}
impl WrappingU32 {
pub fn new(value: u32) -> Self {
Self { value }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ops() {
let x = WrappingU32::new(42);
let y = WrappingU32::new(31);
let z = WrappingU32::new(u32::MAX);
assert_eq!(x + y + y + z, WrappingU32::new(103));
}
}
풀이
use std::ops::Add;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct WrappingU32 {
value: u32,
}
impl WrappingU32 {
pub fn new(value: u32) -> Self {
Self { value }
}
}
impl Add for WrappingU32 {
type Output = Self;
fn add(self, other: WrappingU32) -> Self {
Self{
value: self.value.wrapping_add(other.value)
}
}
}
y
를 두 번 더하고 있는데, WrappingU32
는 u32
를 담고 있으므로 Copy
트레이트를 사용할 수 있다. Copy
를 구현하려면 Clone
도 구현해야 하므로 모두 derive
로 구현한다.
또한 테스트를 위해서 Debug
, PartialEq
트레이트 역시 구현해야 한다.
그리고 새로운 타입이므로 덧셈 연산 역시 새로 정의해야 하는데, z
가 u32
의 최댓값이므로 그냥 더해버리면 오버플로우가 발생한다. wrapping_add()
메서드로 래핑하여 오버플로우를 해결한다.