Làm thế nào để đảm bảo mọi biến thể enum có thể được trả về từ một hàm cụ thể tại thời gian biên dịch?


8

Tôi có một enum:

enum Operation {
    Add,
    Subtract,
}

impl Operation {
    fn from(s: &str) -> Result<Self, &str> {
        match s {
            "+" => Ok(Self::Add),
            "-" => Ok(Self::Subtract),
            _ => Err("Invalid operation"),
        }
    }
}

Tôi muốn đảm bảo tại thời điểm biên dịch rằng mọi biến thể enum được xử lý trong fromhàm.

Tại sao tôi cần cái này? Ví dụ: tôi có thể thêm một Productthao tác và quên xử lý trường hợp này trong fromhàm:

enum Operation {
    // ...
    Product,
}

impl Operation {
    fn from(s: &str) -> Result<Self, &str> {
        // No changes, I forgot to add a match arm for `Product`.
        match s {
            "+" => Ok(Self::Add),
            "-" => Ok(Self::Subtract),
            _ => Err("Invalid operation"),
        }
    }
}

Có thể đảm bảo rằng biểu thức khớp trả về mọi biến thể của enum không? Nếu không, cách tốt nhất để bắt chước hành vi này là gì?

Câu trả lời:


13

Một giải pháp sẽ là tạo ra toàn bộ bảng liệt kê, các biến thể và nhánh dịch thuật với một macro:

macro_rules! operations {
    (
        $($name:ident: $chr:expr)*
    ) => {
        #[derive(Debug)]
        pub enum Operation {
            $($name,)*
        }
        impl Operation {
            fn from(s: &str) -> Result<Self, &str> {
                match s {
                    $($chr => Ok(Self::$name),)*
                    _ => Err("Invalid operation"),
                }
            }
        }
    }
}

operations! {
    Add: "+"
    Subtract: "-"
}

Cách này thêm một biến thể là chuyện nhỏ và bạn không thể quên phân tích cú pháp. Đó cũng là một giải pháp rất KHÔ.

Thật dễ dàng để mở rộng cấu trúc này với các chức năng khác (ví dụ như dịch ngược) mà bạn chắc chắn sẽ cần sau này và bạn sẽ không phải sao chép char phân tích cú pháp.

sân chơi


1
Tôi sẽ để lại câu trả lời của tôi, nhưng điều này chắc chắn tốt hơn!
Peter Hall

12

Mặc dù chắc chắn có một cách phức tạp - và mong manh - để kiểm tra mã của bạn bằng các macro thủ tục, một cách tốt hơn nhiều là sử dụng các bài kiểm tra. Các thử nghiệm mạnh mẽ hơn, viết nhanh hơn nhiều và sẽ xác minh các trường hợp mà mỗi biến thể được trả về, không chỉ là nó xuất hiện ở đâu đó.

Nếu bạn lo ngại rằng các bài kiểm tra có thể tiếp tục vượt qua sau khi bạn thêm các biến thể mới vào enum, bạn có thể sử dụng macro để đảm bảo rằng tất cả các trường hợp đều được kiểm tra:

#[derive(PartialEq, Debug)]
enum Operation {
    Add,
    Subtract,
}

impl Operation {
    fn from(s: &str) -> Result<Self, &str> {
        match s {
            "+" => Ok(Self::Add),
            "-" => Ok(Self::Subtract),
            _ => Err("Invalid operation"),
        }
    }
}

macro_rules! ensure_mapping {
    ($($str: literal => $variant: path),+ $(,)?) => {
        // assert that the given strings produce the expected variants
        $(assert_eq!(Operation::from($str), Ok($variant));)+

        // this generated fn will never be called but will produce a 
        // non-exhaustive pattern error if you've missed a variant
        fn check_all_covered(op: Operation) {
            match op {
                $($variant => {})+
            };
        }
    }
}

#[test]
fn all_variants_are_returned_by_from() {
   ensure_mapping! {
      "+" => Operation::Add,
       "-" => Operation::Subtract,
   }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.