
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 programmers
xhjqf_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()