Синтаксические размышления о типах-представлениях (View Types)
— 2025-04-04 Yoshua Wuyts Источник
Вот одно глупенькое маленькое прозрение, которое у меня было на днях: если прищуриться, то и Типы-представления (View Types), и Типы-паттерны (Pattern Types) выглядят как облегчённые формы Уточнённых типов (Refinement Types)1. Оба позволят ограничивать типы, но слегка разными и дополняющими друг друга способами. Давайте взглянем на это на примере структуры Rgb, содержащей поля для отдельных каналов Красного, Зелёного и Синего, хранящихся как usize2:
#![allow(unused)] fn main() { #[derive(Debug, Default)] struct Rgb { r: usize, g: usize, b: usize, } }
Типы-паттерны дадут нам возможность напрямую использовать такие вещи, как диапазоны или элементы перечислений, в сигнатурах типов. В следующем примере тип usize уточняется так, чтобы статически разрешать только значения от 0 до 255. Это похоже на типы NonZero* в стандартной библиотеке, но как часть языка и применимо с произвольными паттернами:
#![allow(unused)] fn main() { impl Rgb { fn set_red(&mut self, num: usize is ..255) { .. } // ^^^^^^^^ паттерн диапазона } }
Типы-представления касаются сегментирования полей, содержащихся в self, чтобы несколько (изменяемых) методов могли работать с одним и тем же типом, не вызывая проблем с проверкой заимствований. В следующем примере мы предоставляем изменяемый доступ к отдельным полям с помощью отдельных методов. Ни один из этих методов не захватывает пересекающиеся заимствования полей в Self. Это означает, что мы можем свободно вызывать все эти методы, наблюдать за их возвращаемыми типами, и у нас не будет ошибок от borrow checker. Вот пример, использующий синтаксис из последнего поста Нико:
#![allow(unused)] fn main() { impl Rgb { fn red_mut(self: &mut { r } Self) -> .. { .. } // ^^^^^ представление fn green_mut(self: &mut { g } Self) -> .. { .. } // ^^^^^ представление fn blue_mut(self: &mut { b } Self) -> .. { .. } // ^^^^^ представление } }
Вот забавный вопрос: что произойдёт, если мы объединим Типы-паттерны и Типы-представления? Оба служат разным целям, и я знаю, что у меня есть случаи, где я хотел бы их совместить. Так как бы это выглядело? В абстрактном виде кажется, что мы получим что-то вроде следующего:
#![allow(unused)] fn main() { impl Rgb { fn foo(self: &mut { r, g } Self is Self { r: ..255, g: ..255, .. }) {} // ^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // представление паттерн } }
Мне это кажется слишком много для чтения. Но также довольно... избыточно? И Типы-представления, и Типы-паттерны здесь уточняют Self. Я бы ожидал, что мы сможем их объединить. А что, если бы Типы-представления и Типы-паттерны вместо этого занимали одну и ту же синтаксическую позицию. Есть причина, по которой Типам-представлениям приходится использовать is, так давайте использовать его. С этим мы могли бы переписать наш предыдущий пример вот так:
#![allow(unused)] fn main() { impl Rgb { fn foo(&mut self is Self { r: ..255, g: ..255, .. }) {} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // представление + паттерн } }
Это кажется мне намного более читаемым. Объединение обеих возможностей здесь кажется гораздо менее хлопотным и, возможно, даже... приятным? Это обновлённое обозначение также повлияло бы на наш предыдущий пример с Типами-представлениями. Используя обновлённую нотацию, это теперь записывалось бы так же, но без использования паттернов:
#![allow(unused)] fn main() { impl Rgb { fn red_mut(&mut self is Self { r, .. }) -> .. { .. } // ^^^^^^^^^ представление fn green_mut(&mut self is Self { g, .. }) -> .. { .. } // ^^^^^^^^^ представление fn blue_mut(&mut self is Self { b, .. }) -> .. { .. } // ^^^^^^^^^ представление } }
Мне кажется, что это довольно аккуратно унифицировало бы то, как мы говорим об уточнениях в сигнатурах функций. В плане нотации это переосмысливает Типы-представления как Типы-паттерны с игнорируемыми полями. Хотя нам не обязательно принимать эту нотацию: больше всего меня волнует, чтобы мы подумали о том, как обе функции предназначены для совместной работы в языке, чтобы создать целостный опыт. Спасибо за чтение!
Примечания
-
Мне нравится термин «лёгкие уточнённые типы» для категории расширений, включающих типы-паттерны и типы-представления. Для меня это напоминает лёгкие формальные методы: менее тщательные, чем полный вариант, но с невероятной отдачей при относительных затраченных усилиях. ← ↩
-
Не слишком задумывайтесь, почему мы храним эти значения как
usize. Это немного глупо. Для целей этого примера просто представьте, что есть какая-то причина, по которой мы должны делать именно так. ← ↩