100 Exercises To Learn Rust - 4.12 copy 풀기

Copy

Copy 트레이트가 구현되어 있으면 러스트는 묵시적으로 복제체를 생성한다. 값의 소유권이 이동된 후에 사용하더라도 복제체가 사용되었으므로 컴파일 에러가 발생하지 않는다.

그렇다고 자동 복제(automatic cloning)이라고 할 수는 없는데, 몇 가지 제약이 있기 때문이다.

  • Copy는 Clone의 서브 트레이트이므로 Clone 역시 구현되어야 한다.
  • 타입이 메모리에서 차지하는 std::mem::size_of 바이트 이외의 추가 리소스(예: 힙 메모리, 파일 핸들 등)를 관리하지 않아야 한다.
  • 타입이 가변 참조여서는 안된다.

이러한 제약 때문에 StringCopy를 구현하지 않는다. 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를 두 번 더하고 있는데, WrappingU32u32를 담고 있으므로 Copy 트레이트를 사용할 수 있다. Copy를 구현하려면 Clone도 구현해야 하므로 모두 derive로 구현한다.

 

또한 테스트를 위해서 Debug, PartialEq 트레이트 역시 구현해야 한다.

 

그리고 새로운 타입이므로 덧셈 연산 역시 새로 정의해야 하는데, zu32의 최댓값이므로 그냥 더해버리면 오버플로우가 발생한다. wrapping_add() 메서드로 래핑하여 오버플로우를 해결한다.