
Size: a a a
std
(alloc
) добавляющий Vec::extend_from_within
наконец-то смерджили (tracking issue)!let mut vec = vec![0, 1, 2, 3, 4];
vec.extend_from_within(2..);
assert_eq!(vec, [0, 1, 2, 3, 4, 2, 3, 4]);
package main
import (
"fmt"
)
const (
a = 0
b = 1 << iota
_
// Oh hi Mark
c
)
const (
d = iota
)
func main() {
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
}
Вывод:0
2
8
0
Данный пример демонстрирует, что:iota
автоматически становится числом, соответствующим номеру непустой строки без комментариевiota
всё равно будет инкрементированаiota
действует только в рамках одного блока деклараций констант, в следующем блоке он сбрасываетсяconst (
FLAG_A = 1 << iota
FLAG_B
FLAG_C
FLAG_D
)
Возможность пропустить константу позволяет удалить битовый флаг, не меняя значения остальных флагов.iota
в выражениях — это путь сильных, точнее, путь Толяна, поскольку в этом случае требуется вручную делать рекурсивный спуск внутрь выражений со скобками. Я же пойду по пути наименьшего сопротивления и обязую пользователя макроса предоставить идентификатор, который будет использован в качестве iota
.iota
, и после генерации очередной константы вызывать макрос на остатке входа, передав инкрементированное значение iota
.iota
, нам также нужно хранить выражение для константы. Оно передаётся дальше при рекурсивном вызове, при этом оно либо замещается текущим, если оно есть, либо используется и передаётся дальше без изменений.iota
нулевым значением, а выражение для константы можно инициализировать самой iota
:macro_rules! iota_consts {
(
$iota:ident: $($tt:tt)*
//^ ^^весь остальной вход
//\- идентификатор для iota
) => {
iota_consts!(
$iota(0, $iota): $($tt)*
);
};
...
Теперь немного подумаем о том, как распознать константу. Обязательные элементы — это ключевое слово const
, имя, двоеточие, за которым следует тип. Также, вообще говоря, требуется знак равно, за которым следует выражение для константы, оканчивающуюся точкой с запятой, но в нашем синтаксисе они опциональны, так что их надо обрабатывать отдельно. Также, помимо обязательных элементов, у константы могут быть различные атрибуты и модификатор видимости. С учётом всего сказанного синтаксис для захвата необходимых составляющих константы выглядит следующим образом:$(#[$attr:meta])*
$vis:vis
const $name:ident : $ty:ty
iota
(ну или как там это выражение назвал пользователь). Вместо того, чтобы вручную сканировать выражению и заменять iota
руками, мы просто предоставим его определение и сделаем его видимым в выражении. Иными словами, мы заведём отдельную константу с именем $iota
. При этом надо иметь в виду две вещи. Во-первых, имя, данное пользователем, почти наверняка не написано в SCREAMING_SNAKE_CASE
, которым обычно пишут имена констант. Во-вторых, это имя вполне может быть не использовано внутри выражения для константы. Для того, чтобы не вызывать предупреждений компилятора, мы заглушим их при помощи #[allow(dead_code, non_upper_case_globals)]
.(
$iota:ident($iota_value:expr, $prev_expr:expr):
$(#[$attr:meta])*
$vis:vis
const $name:ident : $ty:ty; //нет выражения
$($tt:tt)*
) => {
$(#[$attr])*
$vis
const $name: $ty = {
#[allow(dead_code, non_upper_case_globals)]
const $iota: $ty = $iota_value;
$prev_expr //переиспользуем предыдущее выражение
};
iota_consts!(
// vv--- инкрементируем передаваемое значение
$iota($iota_value + 1, $prev_expr):
// ^^- значение передаём без изменений
$($tt)*
);
};
(
$iota:ident($iota_value:expr, $_prev_expr:expr):
$(#[$attr:meta])*
$vis:vis
const $name:ident : $ty:ty = $const_value:expr;
$($tt:tt)*
) => {
$(#[$attr])*
$vis
const $name: $ty = {
#[allow(dead_code, non_upper_case_globals)]
const $iota: $ty = $iota_value;
$const_value //используем текущее определение
};
iota_consts!(
// vv- и передаём его дальше
$iota($iota_value + 1, $const_value):
$($tt)*
);
};
(
$iota:ident($iota_value:expr, $prev_expr:expr):
_; //ничего, совсем ничего! Намеренно игнорируем
$($tt:tt)*
) => {
iota_consts!(
// vv- но обязательно инкрементируем
$iota($iota_value + 1, $prev_expr):
$($tt)*
);
};
(
$_iota:ident($_iota_value:expr, $_prev_expr:expr):
) => {};
$iota
само корректно типизируется, поэтому $iota
получается эффективно безтиповым значением, как мы и хотели.const
на static
и static mut
соответственно.iota_consts!(iota:
const FOO: u32 = 1 << iota; // iota = 0, FOO = 1
static BAR: i32; // iota = 1, BAR = 2
_; // iota = 2
#[allow(dead_code)]
static mut BAZ: i8 = iota + 2; // iota = 3, BAZ = 5
const QUX: i16; // iota = 4, QUX = 6
);
macro_rules! print_consts {
($($name:ident),*) => {
$(println!("{} = {}", stringify!($name), $name);)*
};
}
fn main() {
print_consts! {
FOO,
BAR,
/* BAZ */
QUX
};
}
FOO = 1
BAR = 2
QUX = 6
#[allow(dead_code)]
на BAZ
компилятор не жалуется на неиспользованное определение. Также, очевидно, макрос позволяет определять константы разных типов.iota
приходится давать самому. Неприятно, но не смертельно.const
повторяется для каждого определения. Если убрать возможность генерировать static (mut)?
, то можно облегчить синтаксис, убрав эти ключевые слова.iota
в выражениях для них будет одинаково. Мой макрос такого не умеет. Пока что неясно ни то, как это мешается на практике, ни то, как это поддержать.iota
можно также использовать в выражениях с дробными числами. Вот это уже более существенный недостаток, но его, в принципе, можно обойти кастами по месту.iota_consts!(iota:Этот код вызывает следующую ошибку компиляции:
const A: u8 = iota + u8::MAX;
const B: u8;
);
error: any use of this value will cause an errorКак видите, не смотря на то, что ошибка произошла при вычислении
--> src/lib.rs:189:19
|
24 | / $vis
25 | | const $name: $ty = {
26 | | #[allow(dead_code, non_upper_case_globals)]
27 | | const $iota: $ty = $iota_value;
28 | | $prev_expr
29 | | };
| |__________-
...
189 | const A: u8 = iota + u8::MAX;
| ^^^^^^^^^^^^^^ attempt to compute `1_u8 + u8::MAX`, which would overflow
|
= note: `#[deny(const_err)]` on by default
B
, компилятор указывает на выражение для`A`. В принципе, понятно, откуда это берётся: токены парсятся с исходными спанами, которые не меняются при преобразованиях макроса. К сожалению, это фундаментальное ограничение декларативных макросов, и я не вижу способа решить эту проблему.package main
import (
"fmt"
)
const (
a = 0
b = 1 << iota
_
// Oh hi Mark
c
)
const (
d = iota
)
func main() {
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
}
Вывод:0
2
8
0
Данный пример демонстрирует, что:iota
автоматически становится числом, соответствующим номеру непустой строки без комментариевiota
всё равно будет инкрементированаiota
действует только в рамках одного блока деклараций констант, в следующем блоке он сбрасываетсяconst (
FLAG_A = 1 << iota
FLAG_B
FLAG_C
FLAG_D
)
Возможность пропустить константу позволяет удалить битовый флаг, не меняя значения остальных флагов.iota
в выражениях — это путь сильных, точнее, путь Толяна, поскольку в этом случае требуется вручную делать рекурсивный спуск внутрь выражений со скобками. Я же пойду по пути наименьшего сопротивления и обязую пользователя макроса предоставить идентификатор, который будет использован в качестве iota
.iota
, и после генерации очередной константы вызывать макрос на остатке входа, передав инкрементированное значение iota
.iota
, нам также нужно хранить выражение для константы. Оно передаётся дальше при рекурсивном вызове, при этом оно либо замещается текущим, если оно есть, либо используется и передаётся дальше без изменений.iota
нулевым значением, а выражение для константы можно инициализировать самой iota
:macro_rules! iota_consts {
(
$iota:ident: $($tt:tt)*
//^ ^^весь остальной вход
//\- идентификатор для iota
) => {
iota_consts!(
$iota(0, $iota): $($tt)*
);
};
...
Теперь немного подумаем о том, как распознать константу. Обязательные элементы — это ключевое слово const
, имя, двоеточие, за которым следует тип. Также, вообще говоря, требуется знак равно, за которым следует выражение для константы, оканчивающуюся точкой с запятой, но в нашем синтаксисе они опциональны, так что их надо обрабатывать отдельно. Также, помимо обязательных элементов, у константы могут быть различные атрибуты и модификатор видимости. С учётом всего сказанного синтаксис для захвата необходимых составляющих константы выглядит следующим образом:$(#[$attr:meta])*
$vis:vis
const $name:ident : $ty:ty