Вы что, правда думали, что за всеми празднествами я оставлю вас без поста? Не скрою, идея заманчивая, но я решил ей не поддаваться... Хотя к тому моменту, как я закончу писать этот пост, уже наверняка наступит 6 декабря... Впрочем, достаточно прелюдий — переходим к постановке задачи!
Иногда нам требуется сопоставить значение с одной из строк... Но при этом игнорируя регистр символов. В общем случае это довольно сложная задача, и даже не из-за зависимости от локали, а просто от сложности правил перевода символов из одного регистра в другой. Пока что забьём на это и будем рассматривать только ASCII-строки. Что нам требуется? Чтобы:
а) чтобы можно было сопоставить (ASCII) строку, невзирая на её регистр;
б) чтобы нас предупреждал компилятор о перекрывающихся паттернах (а вот это уже интересно — компилятор требует точного совпадения паттернов для проверки);
в) чтобы по возможности сохранить возможности, предоставляемые
match.Итак, как нам проверить, что строки неодинаковы с точностью до регистра? В принципе, можно сделать уже известным способом const assert, используя соответствующие const fn, но так как я человек ленивый, я пойду по лёгкому пути: я проверю, что все паттерны на самом деле в нижнем регистре, а проверить их уникальность оставлю компилятору.
Итак, переходим к подзадаче: убедиться, что строка состоит из символов ASCII, но в нижнем регистре, на этапе компиляции. Правда, так как мы хотим использовать в паттернах не только буквы, но и, скажем, цифры, правильнее сказать "из символов ASCII не в верхнем регистре". Для решения части "на этапе компиляции" воспользуемся уже знакомым трюком, который я тут вроде уже показывал: заведём новую константу типа
[(); 1], а в качестве значения ей присвоим [(); condtion as _], где condition — условие, которое нам нужно проверить. Если condition вычисляется в true (и вычисляется на этапе компиляции в принципе), то as _ приводит булево значение к 1usize, получая выражение [(); 1], соответствующее типу. В противном случае false приводится к 0 и выражение принимает вид [(); 0], вызывая ошибку компиляции из-за несовпадения типов. Теперь всё, что нам остаётся для решения это подзадачи — написать функцию, которую можно вызвать на этапе компиляции и которая проверяет указанное выше условие. Написать такую функцию несколько неудобно из-за ограничений const fn (в частности, мы не можем использовать итераторы), но вполне возможно:const fn is_ascii_lowercase(s: &str) -> bool {
let s = s.as_bytes();
let len = s.len();
let mut i = 0;
while i < len {
if !s[i].is_ascii() || s[i].is_ascii_uppercase() {
return false;
}
i += 1;
}
true
}
Ладно, а как нам проверить, что несколько строк записаны в ASCII lowercase? Ну как-как, принимаем список и проходимся о нему:const fn are_all_ascii_lowercase(ss: &[&str]) -> bool {
let len = ss.len();
let mut i = 0;
while i < len {
if !is_ascii_lowercase(&ss[i]) {
return false;
}
i += 1;
}
true
}
Окей, с этой подзадачей мы разобрались. Как нам теперь убедиться, что все строки разные? А эту задачу мы уже решали: генерируем функцию, которая разбирает строку, и подсовываем в match наши строки — и компилятор всё прекрасно проверяет за нас!