유형이 있는 녹 방지제 도안

39842 단어 rustpatterns

소개


녹은 매우 풍부한 유형 시스템을 가지고 있다.그것은 또한 이동의 의미를 가지고 있다.이 두 가지 기능을 모두 사용하면 생성기 모드를 사용하여 API를 구축할 수 있습니다.우리는 왜 구축기 모델을 사용해야 하는지, 그리고 is를 어떻게 실현하는지 설명하는 실제 예시를 보겠습니다. (알겠습니다. 저는 당신에게 그것을 실현하는 방법을 보여 드렸습니다. 반드시 가장 좋은 방법은 아닙니다.)

인스턴스


만약 우리가 하나의 함수를 가지고 있다면 많은 매개 변수를 필요로 한다. 그 중 일부는 필수적이고, 다른 일부는 선택할 수 있다.예:
fn cook_pasta(
    pasta_type: String,
    pasta_name: Option<String>,
    pasta_length: u64,
    altitude: u64,
    water_type: Option<String>,
) {
    // your code here
}
이 방법은 선택할 수 있는 두 개의 매개 변수와 세 개의 강제 매개 변수가 있다.사용하기 어려워:
cook_pasta("Penne".to_owned(), None, 100, 300, Some("Salty".to_owned()));
문제는 이 방법을 통해 선택적 필드 pasta_name 를 무시하고 다른 선택적 필드 water_type 를 명확하게 지정해야 한다는 것입니다.옵션으로 선택할 수 있는 필드를 Some() 로 포장해야 합니다.위치 필드도 설명하기 어렵다.예를 들어 우리는 두 개의 u64 필드가 있다. 만약 방법을 보지 않고 서명하지 않으면 무엇이 무엇인지 판단하기 어렵다.100은 pasta_length 을 가리키는 것입니까 아니면 altitude 을 가리키는 것입니까?
이것은 가독성을 방해한다. 왜냐하면 우리는 코드에서 왔다갔다해야 무슨 일이 일어났는지 알 수 있기 때문이다.
빌더 모드를 통해 다음과 같은 목표를 달성하고자 합니다.
cook_pasta()
    .with_pasta_type("Penne".to_owned())
    .with_pasta_length(100)
    .with_altitude(300)
    .with_water_type("Salty".to_owned())
    .execute();
이런 문법은 확실히 더 좋다. 우리는 무엇이 pasta_length(100)이고 무엇이 altitude(300)인지 한눈에 알 수 있다.또한 선택 가능한 필드를 Some() 에 포장할 필요도, 함수에 전달하기 싫은 선택 필드 None 에 사용할 필요도 없습니다.
가독성이 더 좋지만 추가 execute() 방법이 필요합니다.그런데 어떻게 이루어질까요?

생성기 구조


이곳의 비결은 builder 대상이 있는데 우리는 끊임없이 이동하고 이 과정에서 필드를 추가한다.builder 객체에는 모든 메소드 필드가 있습니다.다음과 같이 하십시오.
#[derive(Debug, Clone, Default)]
struct CookPastaBuilder {
    pub pasta_type: String,
    pub pasta_name: Option<String>,
    pub pasta_length: u64,
    pub altitude: u64,
    pub water_type: Option<String>,
}
현재 out cook_pasta() 함수 (이를 cook_pasta2() 라고 하는데 이전 버전과 구별됩니다) 는 이 구조를 만드는 기본 실례일 뿐입니다.
fn cook_pasta2() -> CookPastaBuilder {
    CookPastaBuilder::default()
}
우리의 cook_pasta 코드는 위에서 정의한 CookPastaBuilder 를 사용하여 실행됩니다.
impl CookPastaBuilder {
    fn execute(&self) {
        // your code here
    }
}
이제 다음과 같이 사용할 수 있습니다.
let mut cpb = cook_pasta2();
cpb.water_type = Some("Penne".to_owned());
cpb.pasta_length = 100;
cpb.altitude = 300;
cpb.water_type = Some("Salty".to_owned());

cpb.execute();
완전히 우리가 원하는 것은 아니지만, 원래의 방법보다 더 좋다.이 해결 방안에는 두 가지 문제가 있습니다. 우선, 필수 필드를 강제로 실행할 수 없습니다.필수 필드를 설정하는 것을 잊어버리면, 실행할 때 그것을 발견할 수 있습니다. (엉망, 엉망, 엉망!)그 다음으로 문법이 번거롭다.우리는 우선 두 번째 문제를 해결할 것이다.

사방을 돌아다니다


우리는 내부장을 방문하는 것이 아니라 이동하는 방법으로 내부장을 폭로할 수 있다.예:
impl CookPastaBuilder {
    fn with_pasta_type(self, pasta_type: String) -> CookPastaBuilder {
        CookPastaBuilder {
            pasta_type,
            pasta_name: self.pasta_name,
            pasta_length: self.pasta_length,
            altitude: self.altitude,
            water_type: self.water_type,
        }
    }
세 가지 주의:
  • 우리는 현재 self 대상을 소비하고 있다.이것은 우리가 더 이상 그 호출 방법 (방법 정의 중의 self 을 사용할 수 없다는 것을 의미한다.
  • 우리는 이전 필드의 모든 필드를 복사하는 새로운 CookPastaBuilder 필드를 만들었지만, 우리가 분배할 필드는 제외한다(예 pasta_type.
  • 우리는 새로 생성된 CookPastaBuilder 을 호출자에게 전달한다.이렇게 하면 우리는 같은 대상에 있는 것처럼 이 호출들을 연결할 수 있다.사실 우리는 매번 통화에서 바뀌고 있다CookPastaBuilder.
  • 우리는 또한 Some() 포장을 제거하여 setter 함수에 넣을 수 있다.
        fn with_water_type(self, water_type: String) -> CookPastaBuilder {
            CookPastaBuilder {
                pasta_type: self.pasta_type,
                pasta_name: self.pasta_name,
                pasta_length: self.pasta_length,
                altitude: self.altitude,
                water_type: Some(water_type),
            }
    
    이제 우리는 이렇게 할 수 있다.
    cook_pasta2()
            .with_pasta_type("Penne".to_owned())
            .with_pasta_length(100)
            .with_altitude(300)
            .with_water_type("Salty".to_owned())
            .execute();
    
    아름답다
    그러나 우리는 여전히 강제적인 현장 검사 문제를 해결해야 한다.예를 들어, 다음과 같이 컴파일할 수 있습니다.
    cook_pasta2().execute();
    
    그러나 이것은 모두 좋지 않다. (예를 들어 처음부터 스파게티를 먹지 않은 상태에서 스파게티를 요리하는 것은 좋지 않다.)

    모든 유형


    지금 우리는 하나가 아니라 두 개builder가 있다고 가정해 보세요.CookPastaBuilderWithoutPastaTypeCookPastaBuilderWithPastaType(네, 썩은 거 알아요. 하지만 용서해 주세요.
    만약 우리가 execute 변체에서만 WithPasta 방법을 정의한다면, 우리는 WithoutPasta 변체에서 그것을 상향 조정해서 사용할 수 있는 사람이 없다는 것을 확보할 수 있다.번역할 때.이거 좋아요.
    따라서 우리의 논리적 절차는 다음과 같다.
  • 호출cook_pasta2()이 생성됩니다CookPastaBuilderWithoutPastaType.
  • 호출with_pasta_type(..)이 소모CookPastaBuilderWithoutPastaType되며 CookPastaBuilderWithPastaType로 돌아갑니다.
  • 현재 호출execute()이 작용할 것이다. CookPastaBuilderWitPastaType이 이 방법을 실현했기 때문이다.
  • 만약 우리가 먼저 execute() 를 호출하고 with_pasta_type(..) 를 호출하지 않는다면, 우리는 컴파일러 오류를 얻을 것이다.
    하지만, 헤헤, 우리는 방금 필수 필드를 처리했다.우리 세 개 있어!물론 당신은 CookPastaBuilderWithoutPastaTypeWithoutAltitudeWithoutPastaLengthCookPastaBuilderWithPastaTypeWithoutAltitudeWithPastaLength 등 모든 교환을 생각해 낼 수 있습니다 (이 예에서 12)...하지만 더 좋은 방법은 키 횟수를 줄일 수 있다.

    모조약?


    우리는 범형을 사용하여 더욱 좋은 절충을 실현할 수 있다.세 개의 필수 필드가 있습니다.
  • CookPastaBuilderWithoutPastaTypeWithoutAltitudeWithPastaLength
  • pasta_type
  • pasta_length
  • 우리는 볼 형식으로 아날로그 값이 존재하거나 존재하지 않을 수 있다.유사:
    #[derive(Debug, Default)]
    pub struct Yes;
    #[derive(Debug, Default)]
    pub struct No;
    
    우리의 새로운 과정은 다음과 같다.
    struct CookPastaBuilder<PASTA_TYPE_SET, PASTA_LENGTH_SET, ALTITUDE_SET>
    
    이러한 범용 유형은 latitude 또는 No 으로 지정된 특정 필드를 나타낼 수 있습니다.예를 들어, 설정 Yes 필드:
        fn with_pasta_type(
            self,
            pasta_type: String,
        ) -> CookPastaBuilder<Yes, PASTA_LENGTH_SET, ALTITUDE_SET> {
            CookPastaBuilder {
                pasta_type,
                pasta_name: self.pasta_name,
                pasta_length: self.pasta_length,
                altitude: self.altitude,
                water_type: self.water_type,
            }
        }
    
    이 함수는 새로운 pasta_type 실례를 되돌려줍니다. 그 중에서 CookPastaBuilderPASTA_NAME_SET 로 설정되어 있기 때문에 필드가 설정되었음을 알 수 있습니다.이 예에서 우리는 이 필드의 기본 형식을 변경하고 싶지 않기 때문에 YesPASTA_LENGHT_SET 으로 돌아갑니다. (이것들은 설정할 수도 있고 설정하지 않을 수도 있습니다. 여기에서는 변경하지 않습니다.)
    각 필수 필드에 대해 이 작업을 수행하면 다음과 같은 유형을 얻을 수 있습니다.
    CookPastaBuilder<Yes, Yes, Yes>
    
    이것은 호출 프로그램이 모든 필수 필드를 설정했다는 것을 컴파일할 때 보증합니다.우리가 해야 할 일은 ALTITUDE_SET 함수의 실행을 이 특정 유형으로 제약하는 것이다.
    impl CookPastaBuilder<Yes, Yes, Yes> {
        fn execute(&self) {
            // your code here
        }
    }
    

    결과


    클라이언트의 관점에서 볼 때, 우리의 API는 여전히 똑같다. 이런 호출은 작동할 수 있다.
    cook_pasta2()
        .with_pasta_type("Penne".to_owned())
        .with_pasta_length(100)
        .with_altitude(300)
        .with_water_type("Salty".to_owned())
        .execute();
    
    그러나 이것은 우리가 강제 execute() 파라미터를 설정하는 것을 잊어버렸기 때문이 아니다.
    cook_pasta2()
        .with_pasta_type("Penne".to_owned())
        .with_pasta_length(100)
        .with_water_type("Salty".to_owned())
        .execute();
    
    오류는 다음과 같습니다.
    altitude 도움이 되지는 않지만, 적어도 실행하기 전에 오류를 포착했습니다.최소한 범용 유형 이름은 다음 중 무엇이 부족한지 확인하는 데 도움이 됩니다.

    즉, 구조 선언에 필요한 no method named execute found for type CookPastaBuilder<Yes, Yes, No> in the current scope 을 추가하는 경우:
    #[derive(Debug, Clone, Default)]
    pub struct CookPastaBuilder<PASTA_TYPE_SET, PASTA_LENGTH_SET, ALTITUDE_SET>
    where
        PASTA_TYPE_SET: ToAssign,
        PASTA_LENGTH_SET: ToAssign,
        ALTITUDE_SET: ToAssign,
    {
        pasta_type_set: PhantomData<PASTA_TYPE_SET>,
        pasta_length_set: PhantomData<PASTA_LENGTH_SET>,
        altitude_set: PhantomData<ALTITUDE_SET>,
    
        pasta_type: String,
        pasta_name: Option<String>,
        pasta_length: u64,
        altitude: u64,
        water_type: Option<String>,
    }
    

    마지막 점: 유령


    이것이 바로 우리가 원하는 것이다. 우아한 문법과 번역을 할 때 검사하는 생성기 모델이다.그러나 우리가 사용하는 것은 위범형이다. 이것은 우리의 코드에서 적극적으로 사용하지 않은 범형이다.Rust는 이에 대해 불만을 표시했기 때문에 그를 기쁘게 하기 위해 세 개의 새로운 필드를 추가했다. 각 범용 필드는 하나씩WHERE이다.걱정하지 마세요. 운행할 때 나타나지 않기 때문에 추가하는 데는 비용이 없습니다.
    만약 이것이라면, 최종 코드이다.너의 생각을 나에게 알려줘!
    use std::fmt::Debug;
    use std::marker::PhantomData;
    
    #[derive(Debug, Default)]
    pub struct Yes;
    #[derive(Debug, Default)]
    pub struct No;
    
    pub trait ToAssign: Debug {}
    pub trait Assigned: ToAssign {}
    pub trait NotAssigned: ToAssign {}
    
    impl ToAssign for Yes {}
    impl ToAssign for No {}
    
    impl Assigned for Yes {}
    impl NotAssigned for No {}
    
    pub fn cook_pasta(
        pasta_type: String,
        pasta_name: Option<String>,
        pasta_length: u64,
        altitude: u64,
        water_type: Option<String>,
    ) {
        // your code here
        println!(
            "cooking pasta! -> {:?}, {:?}, {:?}, {:?}, {:?}",
            pasta_type, pasta_name, pasta_length, altitude, water_type
        );
    }
    
    #[derive(Debug, Clone, Default)]
    pub struct CookPastaBuilder<PASTA_TYPE_SET, PASTA_LENGTH_SET, ALTITUDE_SET>
    where
        PASTA_TYPE_SET: ToAssign,
        PASTA_LENGTH_SET: ToAssign,
        ALTITUDE_SET: ToAssign,
    {
        pasta_type_set: PhantomData<PASTA_TYPE_SET>,
        pasta_length_set: PhantomData<PASTA_LENGTH_SET>,
        altitude_set: PhantomData<ALTITUDE_SET>,
    
        pasta_type: String,
        pasta_name: Option<String>,
        pasta_length: u64,
        altitude: u64,
        water_type: Option<String>,
    }
    
    impl<PASTA_TYPE_SET, PASTA_LENGTH_SET, ALTITUDE_SET>
        CookPastaBuilder<PASTA_TYPE_SET, PASTA_LENGTH_SET, ALTITUDE_SET>
    where
        PASTA_TYPE_SET: ToAssign,
        PASTA_LENGTH_SET: ToAssign,
        ALTITUDE_SET: ToAssign,
    {
        pub fn with_pasta_type(
            self,
            pasta_type: String,
        ) -> CookPastaBuilder<Yes, PASTA_LENGTH_SET, ALTITUDE_SET> {
            CookPastaBuilder {
                pasta_type_set: PhantomData {},
                pasta_length_set: PhantomData {},
                altitude_set: PhantomData {},
                pasta_type,
                pasta_name: self.pasta_name,
                pasta_length: self.pasta_length,
                altitude: self.altitude,
                water_type: self.water_type,
            }
        }
    
        pub fn with_pasta_name(
            self,
            pasta_name: String,
        ) -> CookPastaBuilder<PASTA_TYPE_SET, PASTA_LENGTH_SET, ALTITUDE_SET> {
            CookPastaBuilder {
                pasta_type_set: PhantomData {},
                pasta_length_set: PhantomData {},
                altitude_set: PhantomData {},
                pasta_type: self.pasta_type,
                pasta_name: Some(pasta_name),
                pasta_length: self.pasta_length,
                altitude: self.altitude,
                water_type: self.water_type,
            }
        }
    
        pub fn with_pasta_length(
            self,
            pasta_length: u64,
        ) -> CookPastaBuilder<PASTA_TYPE_SET, Yes, ALTITUDE_SET> {
            CookPastaBuilder {
                pasta_type_set: PhantomData {},
                pasta_length_set: PhantomData {},
                altitude_set: PhantomData {},
                pasta_type: self.pasta_type,
                pasta_name: self.pasta_name,
                pasta_length,
                altitude: self.altitude,
                water_type: self.water_type,
            }
        }
    
        pub fn with_altitude(
            self,
            altitude: u64,
        ) -> CookPastaBuilder<PASTA_TYPE_SET, PASTA_LENGTH_SET, Yes> {
            CookPastaBuilder {
                pasta_type_set: PhantomData {},
                pasta_length_set: PhantomData {},
                altitude_set: PhantomData {},
                pasta_type: self.pasta_type,
                pasta_name: self.pasta_name,
                pasta_length: self.pasta_length,
                altitude,
                water_type: self.water_type,
            }
        }
    
        pub fn with_water_type(
            self,
            water_type: String,
        ) -> CookPastaBuilder<PASTA_TYPE_SET, PASTA_LENGTH_SET, ALTITUDE_SET> {
            CookPastaBuilder {
                pasta_type_set: PhantomData {},
                pasta_length_set: PhantomData {},
                altitude_set: PhantomData {},
                pasta_type: self.pasta_type,
                pasta_name: self.pasta_name,
                pasta_length: self.pasta_length,
                altitude: self.altitude,
                water_type: Some(water_type),
            }
        }
    }
    
    impl CookPastaBuilder<Yes, Yes, Yes> {
        pub fn execute(&self) {
            // your code here
            println!("cooking pasta! -> {:?}", self);
        }
    }
    
    pub fn cook_pasta2() -> CookPastaBuilder<No, No, No> {
        CookPastaBuilder::default()
    }
    
    fn main() {
        cook_pasta("Penne".to_owned(), None, 100, 300, Some("Salty".to_owned()));
    
        cook_pasta2()
            .with_pasta_type("Penne".to_owned())
            .with_pasta_length(100)
            .with_water_type("Salty".to_owned())
            .with_altitude(300)
            .execute();
    }
    
    즐거움 코드
    프랑시스코 코노

    좋은 웹페이지 즐겨찾기