Совпадение по двум параметрам и диапазонам

Я пытаюсь реализовать черту квадранта для двумерной точки и не понимаю, как сопоставить более двух полей структуры с диапазонами. Моя структура определяется как:

#[derive(Debug)]
struct Point<T, U> {
  x: T, // so I can have float and int for x and y, sure
  y: U,
}

impl<T, U> Point<T, U> {
    fn x(&self) -> &T {
        &self.x // make convenient accessors
    }
    
    fn y(&self) -> &U {
        &self.y
    }
}

Я определяю эту черту:

pub trait Quadrant {
    fn quadrant(&self) -> String; // define the trait
}

И я пытаюсь реализовать это для структуры Point:

//now implement the trait for your point
impl<T,U> Quadrant for Point<T,U> {
    fn quadrant(&self) -> String {
        match self {
            (self::x() > 0 && self::y() > 0) => "I",
            (self::x() > 0 && self::y() < 0) => "IV",
            (self::x() < 0 && self::y() > 0) => "II",
            (self::x() < 0 && self::y() < 0) => "III",
            (self::x() == 0 && self::y() == 0) => "ORIGIN",
            _ => "AXIS",
        }
    }
}

Моя ошибка:

(self::x() > 0 && self::y() > 0) => "I",
           ^ expected one of `)`, `,`, or `|`, found `>`

Мне непонятно, как сделать этот блок соответствия на self.x() и self.y() для всех диапазонов.

Я хотел бы иметь возможность сказать:

fn main() {
  let p1 = Point { x: 5, y: 10 };
  println!("p1 is in Quadrant = {:?}", p1.quadrant());
}

С выходом p1 is in Quadrant I.

Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
2
0
76
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Плечи спичечного блока должны иметь форму <pattern> [conditions]. Если все, что у вас есть, это условия, вы можете написать старую простую цепочку if-else:

fn quadrant(&self) -> &'static str {
    if self.x() > 0 && self.y() > 0 {
        "I"
    } else if self.x() < 0 && self.y() > 0 {
        "II"
    } else if self.x() < 0 && self.y() < 0 {
        "III"
    } else if self.x() > 0 && self.y() < 0 {
        "IV"
    } else if self.x() == 0 && self.y() == 0 {
        "ORIGIN"
    } else {
        "AXIS"
    }
}

Чтобы блок match имел смысл, вам нужно вытащить одно или несколько значений, чтобы было что-то, с чем можно сопоставить шаблон. Вы можете поместить координаты x и y в кортеж:

fn quadrant(&self) -> &'static str {
    match (self.x(), self.y()) {
        (x, y) if x > 0 && y > 0 => "I",
        (x, y) if x < 0 && y > 0 => "II",
        (x, y) if x < 0 && y < 0 => "III",
        (x, y) if x > 0 && y < 0 => "IV",
        (0, 0) => "ORIGIN",
        _ => "AXIS",
    }
}

Это работает, но это просто замаскированная цепочка if-else. Было бы лучше, если бы он не повторял сравнения в каждой руке. Мы можем сделать его правильным match блоком, вызвав cmp и сопоставив значения Ordering:

fn quadrant(&self) -> &'static str {
    use std::cmp::Ordering::{Equal, Greater, Less};
    
    match (self.x().cmp(&0), self.y().cmp(&0)) {
        (Greater, Greater) => "I",
        (Less, Greater) => "II",
        (Less, Less) => "III",
        (Greater, Less) => "IV",
        (Equal, Equal) => "ORIGIN",
        (Equal, _) | (_, Equal) => "AXIS",
    }
}

Что мне нравится в этом, так это то, что становится ясно, что "AXIS" — это то, где одно значение или другое равно нулю. Написав это таким образом, а не с помощью универсальной _ => "AXIS" руки, компилятор может проверить, что мы рассмотрели все возможности. Если бы мы пропустили случай, код не скомпилировался бы.

Другие примечания:

  • Вызывайте методы получателя с помощью self.x(), а не self::x(). Двоеточия предназначены для вызова статических методов, как в Self::x(), если x не принимает параметр &self.

  • Если вы вернете &'static str вместо String, вы можете напрямую вернуть литералы. Если возвращаемый тип — String, вам нужно вызвать String::from или .into() или один из других методов String-конструкции, чтобы преобразовать статические литералы в выделенные в куче Strings.

  • Еще лучше сделать Quadrant перечислением вместо признака, чтобы вы могли возвращать строго типизированные значения перечисления.

  • Я бы рекомендовал сделать x и y общедоступными переменными и отказаться от геттеров. Сокрытие переменных и предоставление геттеров — это антипаттерн в Rust. Это не всегда неправильно, но это не то, что следует делать обычно, как в объектно-ориентированном языке, который способствует тяжелой инкапсуляции.

    struct Point<T, U> {
        pub x: T,
        pub y: U,
    }
    

Это имеет большой смысл. Когда я делаю x и y общедоступными, я получаю эту ошибку, которую я не понимаю: `match (self.x.cmp(&0), self.y.cmp(&0)) { ^^^ T не является итератором `

Mittenchops 17.10.2022 03:36

Это отличный ответ. Думаю, я подниму свои последующие вопросы во втором вопросе. Спасибо!

Mittenchops 17.10.2022 04:03

Другие вопросы по теме