Rust 터치만 시도하는 노트

오래전부터 꼭 해야 할 일 중 하나로서 나는 Rust의 접촉만 보았다.C/C++를 타깃으로 바꾼 것만 들었기 때문에 C++로 어느 정도 깨달은 마음으로 시작한다.

목표


새로운 것을 배우기 시작할 때는 무엇을 목표로 해야 할지 늘 고민했지만, 다음 규격을 만족하는 Vector 구조체를 만들기로 했다.
  • 상위 계층의 다른 파일에서 읽을 수 있는 독립형 파일로 제작(모듈)
  • 는 x, y로 구성된 2차원 벡터
  • 를 나타낸다
  • 각 요소는 32비트 부동점수
  • 로 설정한다.
  • 색인으로 요소당 가져오기 허용
  • 조작원이 사칙 연산을 할 수 있도록
  • 문자열로 성형 출력
  • 코드


    상술한 규격을 만족시키는 코드는 다음과 같다.
    use std::ops::{Add, Sub, Mul, Div};
    use std::result::Result;
    use std::fmt::Display;
    
    pub trait VectorF32: {
        fn get_item(&self, index: usize) -> Result<f32, &str>;
        fn get_size(&self) -> usize;
    
        fn get_length(&self) -> f32 {
            self.get_length2().sqrt()
        }
        fn get_length2(&self) -> f32 {
            let mut result = 0.0;
            for i in 0..self.get_size() {
                let v = self.get_item(i).unwrap();
                result += v * v;
            }
    
            result
        }
        fn normalized(&self) -> Self;
    }
    
    #[derive(Debug, Copy, Clone)]
    pub struct Vector2F32 {
        pub x: f32,
        pub y: f32
    }
    
    impl VectorF32 for Vector2F32 {
        fn get_item(&self, index: usize) -> Result<f32, &str> {
            match index {
                0 => Ok(self.x),
                1 => Ok(self.y),
                _ => Err("index is out of bounds")
            }
        }
        
        fn get_size(&self) -> usize { 2 }
    
        fn normalized(&self) -> Self {
            let len = self.get_length();
            return Vector2F32 {
                x: self.x / len,
                y: self.y / len
            }
        }
    }
    
    impl Add for Vector2F32 {
        type Output = Vector2F32;
        fn add(self, other: Vector2F32) -> Vector2F32 { 
            Vector2F32 {
                x: self.x + other.x,
                y: self.y + other.y
            }
        }
    }
    
    impl Sub for Vector2F32 {    
        type Output = Vector2F32;
        fn sub(self, other: Vector2F32) -> Vector2F32 {
             Vector2F32 {
                 x: self.x - other.x,
                 y: self.y - other.y
             }
        }
    }
    
    impl Mul<f32> for Vector2F32 {    
        type Output = Vector2F32;
        fn mul(self: Vector2F32, other: f32) -> Vector2F32 { 
            Vector2F32 {
                x: self.x * other,
                y: self.y * other
            }
        }
    }
    
    impl Mul<Vector2F32> for f32 {    
        type Output = Vector2F32;
        fn mul(self: f32, other: Vector2F32) -> Vector2F32 {
            other * self
        }
    }
    
    impl Div<f32> for Vector2F32 {
        type Output = Vector2F32;
        fn div(self: Vector2F32, other: f32) -> Vector2F32 {
             Vector2F32 {
                 x: self.x / other,
                 y: self.y / other
             }
        }
    }
    
    impl Display for Vector2F32 {    
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { 
            write!(f, "{}, {}", self.x, self.y)
        }
    }
    
    
    mod sub;
    use sub::vector::Vector2F32;
    
    fn main() {
        let vec = Vector2F32 {
            x: 10.0,
            y: 20.0
        };
    
        println!("(10.0, 20.0) + (5.0, 5.0) = ({})", vec + Vector2F32{x:5.0, y:5.0});
        println!("(10.0, 20.0) - (5.0, 5.0) = ({})", vec - Vector2F32{x:5.0, y:5.0});
        println!("(10.0, 20.0) * 2.0        = ({})", vec * 2.0);
        println!("2.0 * (10.0, 20.0)        = ({})", 2.0 * vec);
        println!("(10.0, 20.0) / 2.0        = ({})", vec / 2.0);
    }
    
    
    또한 문서의 구성은 다음과 같다.
    - main.rs
    - sub.rs
    - sub
      - vector.rs
    

    설명


    1. 파일 분할


    다른 파일에 설치된 코드를 사용할 때'mod'로 파일을 지정합니다.main.제일 먼저 간 건 이거야.
    mod sub;
    
    "벡터.rs를 쓰고 싶은데 왜 sub를 읽어요?""원래 sub.rs가 뭐예요?"이런 의문이 있지만 다른 계층의 문서는 직접 읽을 수 없다.
    여기는 sub.rs입니다.
    pub mod vector;
    
    .디렉토리와 이름이 같은 모듈 파일을 만들고 계층 아래의 모듈을 읽어 "이 디렉토리의 모듈 파일 사용"을 표시합니다.그런 다음 <디렉토리 이름>rs를 읽으면 특정 디렉터리의 모듈을 간접적으로 읽을 수 있습니다.
    상세한 상황은 크레인의 노선 모듈의 위치 등과도 관련이 있지만, 우선 이 정도의 이해가 필요하다.
    그러면 파일을 볼 수 있기 때문에use로 이 파일에서 사용할 구조체를 지정합니다.
    // subディレクトリの、vectorモジュールにある、Vector2F32を使う
    use sub::vector::Vector2F32;
    

    2. x, y로 구성된 2차원 벡터를 나타낸다


    구조체에 필드만 포함하면 된다.
    #[derive(Debug, Copy, Clone)]
    pub struct Vector2F32 {
        pub x: f32,
        pub y: f32
    }
    
    구조를pub(public)로 지정하고 이(privete)를 추가하지 않으면 다른 모듈이 보이지 않아 사용할 수 없습니다.또한 필드의 x, y도 모두pub이기 때문에 이것을 추가하지 않으면 각 필드는 밖에서 들어갈 수 없습니다.
    그럼 "derive"는 뭘까요?
    이것은 지정한 트레이트를 이미 정해진 실현 방식으로 제공하도록 지정합니다.
    trait에 관해서는 뒤에서 서술한 바와 같이 여기서 Vector2F32와 같은 구조체에 대해 표준으로 Debug,Copy,Clone의 기능을 제공한다.
    예를 들어 C++ 같은 것은 복제 구조기를 설치하지 않으면 이미 정해진 처리를 수행할 수 있지만 Rust는 복사할 수 없다.복제를 위해서는 Copy와 Clone의 trait가 필요하지만 필드를 복제하면 되고 표준 기능만으로도 충분합니다.

    3. trait 정의


    Rust는 데이터 구조의 정의와 처리의 실현을 분리하여 기술한다.
    전항에서 데이터 구조만 정의했다.처리가 필요하지는 않지만,trait를 성명하고 그것에 따라 실현할 것입니다.
    일단 트랙이 뭐죠?
    일가의 수첩에 따르면'공통된 행동거지를 정의하는 것'이라고 불리지만 C#경험자로서interface+α말하자면 아주 가까운 인상이 있다.
    Vector의interface로서 무엇을 가지고 있어야 하는지를 고려하여 (최소한) 예처럼 실시하였다.
    pub trait VectorF32: {
        // 要素取得
        fn get_item(&self, index: usize) -> Result<f32, &str>;
        // 要素数取得
        fn get_size(&self) -> usize;
        // ノルム取得
        fn get_length(&self) -> f32 {
            self.get_length2().sqrt()
        }
        // ノルムの二乗取得
        fn get_length2(&self) -> f32 {
            let mut result = 0.0;
            for i in 0..self.get_size() {
                let v = self.get_item(i).unwrap();
                result += v * v;
            }
    
            result
        }
        // 正規化ベクトル取得
        fn normalized(&self) -> Self;
    }
    
    방법의 기법은fn メソッド名(引数) -> 返り値;이다.
    이른바 구성원 방법에서 매개 변수의 최초 추출 &self는 방법에서self를 통해 자신에게 접근한다.또한 Self를 매개 변수로 지정하거나 값을 되돌려줌으로써 이 trait를 실현하는 유형을 지정할 수 있습니다.
    또한trait는interface와 달리trait에서 실현할 수 있다.벡터의 모든 요소만 얻으면 범수를 계산할 수 있기 때문에 getitem에서 준비한trait에서 실현할 수 있습니다.

    4. 트레이트 구현


    그러면 구조체 Vector2F32에 대해trait VectorF32를 실시한다.
    impl VectorF32 for Vector2F32 {
        fn get_item(&self, index: usize) -> Result<f32, &str> {
            match index {
                0 => Ok(self.x),
                1 => Ok(self.y),
                _ => Err("index is out of bounds")
            }
        }
        
        fn get_size(&self) -> usize { 2 }
    
        fn normalized(&self) -> Self {
            let len = self.get_length();
            return Vector2F32 {
                x: self.x / len,
                y: self.y / len
            }
        }
    }
    
    trait의 설치impl trait for struct {}에 있습니다.또한 impl struct {}를 통해trait와 무관하게 처리할 수 있다.
    복잡한 일은 안 했지만 걸면 get아이템이야?
    get_item은 색인을 지정하여 요소를 가져오는 방법입니다.요소 수가 2인 만큼 0이나 2 이상 미만으로 지정했을 때 예외를 던지려 했으나 러스트에는 예외라는 개념이 없었다.반대로 되돌아오는 값에 오류 정보를 포함할 수 있습니다.Result<f32, &str> 오류가 없을 때 f32로 돌아가고 오류가 있을 때str의 참조로 돌아갑니다.왜 str가 아니라 참조하는 일은 생략했어?
    Add, Sub, Mult, Div trait를 설치하여 해당하는 네 가지 연산자를 사용할 수 있습니다.
    마찬가지로 Display trait 설치를 통해 포맷기의 매개 변수에 전달할 수 있습니다.
    지금까지 목표로 삼은 규격은 만족했다.

    감상


    스킨십을 조금밖에 안 해봤지만 Rust 분위기가 조금 느껴지는 것 같아요.
    소유권과 생활시간의 사고방식, 그리고 단순한 기법도 어렵지만 성능과 안전성을 고려한 문제도 있다.
    개인의 주요 언어는 차치하고, 지금까지라면 C++가 시행될 수 있다면 Rust에서 시도해 볼 수 있을 것 같다.

    좋은 웹페이지 즐겨찾기