Eugene-Usachev
@Eugene-Usachev

Как написать такой макрос?

Мне нужно написать процедурный макрос, пусть его имя r. Он должен изменить функцию таким образом, чтобы, если она что-то возвращает, она возвращала (), а то, что должна возвращать, записывала под указатель. На примере будет понятнее.

Код:

#[r]
fn bar(a: usize) -> usize {
    if a % 2 == 0 {
        return 0;
    }
    1
}


Должен превращаться в

fn bar(a: usize, res: *mut usize) {
    if a % 2 == 0 {
        unsafe { *res = 0; }
        return;
    }
    unsafe { *res = 1; }
}


Как написать такой макрос? Если не можете выделить время на его написание, скажите в какую сторону смотреть.
  • Вопрос задан
  • 208 просмотров
Решения вопроса 1
Eugene-Usachev
@Eugene-Usachev Автор вопроса
Это частично возможно. Почему частично? Невозможно нормально обработать try-expression (он же знак вопроса) и макросы, которые держат в себе return. В остальном это возможно, но у меня ушло на это 164 строчки кода, а я всё ещё не уверен, что обработал все случаи (неявный возврат может придти из любого блока, находящегося в любом другом блоке; а я не уверен, что моей фантазии хватило на все случаи). Я не нашёл способа сделать это готовой функцией и парсил почти "в лоб руками", используя syn. Если кому интересно, решение кроется в рекурсивной обработке всех syn::Block и всех syn::Expr + syn::Local в нём.

Я не буду прикладывать свой код здесь, так как он, вероятно, не обрабатывает все случаи и работает в худшем случаи за O(2*N) по скорости, но если реализация совсем не идёт в голову, пишите в комментарии этого ответа.
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 1
bingo347
@bingo347
Crazy on performance...
Я бы тут не парился, и превращал бы это:
#[r]
fn bar(a: usize) -> usize {
    if a % 2 == 0 {
        return 0;
    }
    1
}


В это:
fn bar(a: usize, res: *mut usize) {
    fn bar_impl(a: usize) -> usize {
        if a % 2 == 0 {
            return 0;
        }
        1
    }
    unsafe { *res = bar_impl(a); }
}
Главная фишка в том, что исходный код оставляем без изменений, парсить надо только сигнатуру (что с использованием syn - легко).
По сути просто генерируем обёртку. Для универсальности стоит учесть async fn.

Ну и если совсем по хорошему, то тут легко UB поймать с сырым указателем, и генерируемую функцию имеет смысл делать unsafe.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы