
Writing Non-Trivial Macros in Rust — полезная статья о том, как поэтапно писать на Rust (относительно) сложные декларативные макросы. Ссылается на The Little Book of Rust Macros, которая сама по себе достойна прочтения.
Size: a a a
Options
, в которой хранятся опции для конфигурирования поведения парсера. Эта структура перемещается внутрь парсера при создании и в процессе парсинга флаги оттуда только считываются, но никогда не меняется. Звучит, как идеальный кандидат для вынесения на уровень типов.Options
с этого:bitflags::bitflags! {
pub struct Options: u32 {
const ENABLE_TABLES = 1 << 1;
const ENABLE_FOOTNOTES = 1 << 2;
const ENABLE_STRIKETHROUGH = 1 << 3;
const ENABLE_TASKLISTS = 1 << 4;
const ENABLE_SMART_PUNCTUATION = 1 << 5;
const ENABLE_HEADING_ATTRIBUTES = 1 << 6;
}
}
на это:pub struct Options<
const ENABLE_TABLES: bool,
const ENABLE_FOOTNOTES: bool,
const ENABLE_STRIKETHROUGH: bool,
const ENABLE_TASKLISTS: bool,
const ENABLE_SMART_PUNCTUATION: bool,
const ENABLE_HEADING_ATTRIBUTES: bool,
>(());
Там, где в коде был вызов, скажем, options.contains(Options::ENABLE_TABLES)
, в моём варианте стал вызов options.enable_tables()
— метод, который просто возвращает значение параметра ENABLE_TABLES
. Имея на руках доказуемо константные значения, компилятор может убрать из кода if-ы и вместе с ними лишний код, что должно снизить объём генерируемого кода для каждого варианта парсинга и повысить производительность за счёт отсутствия ветвлений.<List as AsStrList<{ n_digits(<N as NumericValue>::VALUE) }>>::LIST
len
, применимая к пяти семействам типов (слайс, массив, строка, мапа, канал), преобразуется в конкретные операции для каждого типа — от парсинга до кодгенаF::<()> { ptr: &(), __: <_>::default() }
Just normal code written by normal rust programmersxhjqf_pointer::subs!!(<T>)&{[]}
, я очень долго ждал этого PR, теперь больше не надо вызывать макрос incl(<<>><T>::__)
!u64
, and each u64
represents 64 contiguous indices. <...> We tried a number of other designs as well. Initially we used a simple dense bitvec, but this was prohibitively expensive: O(n^2) space when the real need is closer to O(n) (i.e., a classic sparse matrix). We also tried a hybrid scheme that kept a vector of indices when small and used either a bitvec or a hashset when large. This did not perform as well because (i) it was less memory-efficient (the chunking helps with this) and (ii) insertions are more expensive when they always require a full hashset/hashmap insert.assert!(() == {});
assert!((42) == {42});
special_characters
. Выглядит она так:fn special_characters() {
let val = !((|(..):(_,_),__@_|__)((&*"\\",'🤔')/**/,{})=={&[..=..][..];})//
;
assert!(!val);
}
Сегодня я разберу, что же тут происходит — в основном в первой строчке, разумеется. Для начала избавимся от комментариев — они тут нужны только ради того, чтобы ещё больше напугать:let val = !((|(..):(_,_),__@_|__)((&*"\\",'🤔'),{})=={&[..=..][..];});
Справа отрицание некоего выражения. Вынесем его в отдельную переменную и заодно уберём наружные скобки:let tmp = (|(..):(_,_),__@_|__)((&*"\\",'🤔'),{})=={&[..=..][..];};
let val = !tmp;
Хм, определение tmp
всё ещё выглядит сложноватым... Может, вызвать rustfmt?let tmp = (|(..): (_, _), __ @ _| __)((&*"\\", '🤔'), {}) == {
&[..= ..][..];
};
Сильно проще не сделало, но зато теперь видно, что корень дерева выражений — это операция равенства. Посмотрим, что тут справа:{
&[..= ..][..];
}
Хм, что тут у нас? У нас тут массив размера 1, в котором лежит выражение ..= ..
(первый рендж, кстати, биндится первым и потому выражение в целом имеет тип RangeToInclusive<RangeFull>), который индексируется RangeFull
, что работает за счёт deref coercion к соответствующему типу слайса, на полученный слайс берётся ссылка...()
.(|(..): (_, _), __ @ _| __)((&*"\\", '🤔'), {})
Здесь у нас после открывающей скобки идёт |
. Так как бинарное выражение с этого оператора начинаться не может, это начала определения замыкания. После заключённого в скобке замыкания идёт её вызов с двумя аргументами. Посмотрим на литерал замыкания поподробнее:|(..): (_, _), __ @ _| __
Между чёрточками тут два аргумента, разделённых запятой. И тут на полной используется тот факт, что на месте аргументов можно использовать не только идентификаторы, но и произвольные (но которые тайпчекаются, разумеется) паттерны. Вот первый аргумент:(..): (_, _)
За аргументами в замыканиях может опционально следовать двоеточие и тип аргумента, причём, в отличие от типов в обычных функциях, он не обязательно должен быть выписан полностью. (_, _)
тут означает двухместный кортеж с типами, которые нужно вывести компилятору.(..)
же — это паттерн, который матчится с кортежем с произвольным количеством полей. Кстати, с кортежными структурами и кортежными вариантами перечислений этот паттерн тоже работает — достаточно добавить имя. Например, вот этот код тайпчекается:struct ManyFields(i32, String, char, u8, i32);Можно даже сматчить только поля из части кортежа:
fn foo(mf: ManyFields) {
let ManyFields(..) = mf;
}
fn ends(mf: &ManyFields) -> (i32, i32) {
match mf {
&ManyFields(start, .., end) => (start, end),
}
}
__ @ _
. Как всегда в @-биндингах (ладно, ладно, at-биндингах), справа от @
находится паттерн — в данном случае _
, который матчится абсолютно со всем — а слева от @
находится имя, которое привязывается к этому значению, __
. Безусловно, идентификатор выглядит странно, но это валидное имя в Rust.|(..): (_, _), __ @ _| __
Тело замыкания состоит из единственного идентификатора __
. Иными словами, замыкание просто возвращает свой второй аргумент. С тем же успехом его можно было бы написать так:|_, second| second
(или, если быть точнее и сохранить тот факт, что первый аргумент является парой, |_: (_, _), second| second
).(замыкание...)((&*"\\", '🤔'), {})
Первый аргумент — это пара (как и ожидается) из &str
и char
. Фрагмент &*"\\"
валиден за счёт того, что строковой литерал имеет тип &'static str
и, соответственно, его можно разыменовать (и взять ссылку от результата). Кстати, тут используется синтаксис для escaping-а, поэтому содержимое строки — это единственный символ \
. А вот второй аргумент — это пустой блок. В Rust он вычисляется в ()
. С учётом того, что замыкание просто возвращает свой второй аргумент, получаем, что часть слева от оператора равенства просто возвращает ()
.val
присваивается отрицание от сравнивания ()
и ()
. Так как unit всегда равен самому себе, в val
записывается значение false
. Строчкой ниже в функции вызывается assert!(!val)
, который ожидаемо проходит.>>> add(1)(2)(3)()
6
>>> add(1)(2)(3)(4)()
10
ну в принципе ничего сложного:def add(succ = None):
acc = getattr(add, "_acc", 0)
if succ is None:
if hasattr(add, "_acc"):
del add._acc
return acc
add._acc = acc + succ
return add
#![feature(fn_traits, unboxed_closures)]
use std::ops::Add;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Adder<T> {
acc: T,
}
impl<L, R, O> FnOnce<(R,)> for Adder<L>
where
L: Add<R, Output = O>,
{
type Output = Adder<O>;
extern "rust-call" fn call_once(self, (succ,): (R,)) -> Self::Output {
Adder {
acc: self.acc + succ,
}
}
}
impl<A> FnOnce<()> for Adder<A> {
type Output = A;
extern "rust-call" fn call_once(self, (): ()) -> Self::Output {
self.acc
}
}
#[allow(non_camel_case_types)]
pub struct add;
impl<A> FnOnce<(A,)> for add {
type Output = Adder<A>;
extern "rust-call" fn call_once(self, (acc,): (A,)) -> Self::Output {
Adder { acc }
}
}
fn main() {
println!("{}", add(1)(2)(3)());
}
единственное, что не получилось сделать - разрешить делать сразу add()