Ниже приведены 6 макросов Rust, на которые стоит взглянуть, чтобы улучшить свой проект. Макросы Rust - отличная функция, которая может уменьшить количество шаблонов кода и сэкономить время программистов. Они также предлагают разработчикам гибкость в использовании метапрограммирования для добавления новых функций в язык и их упаковки таким образом, чтобы их можно было легко интегрировать в код. Это одна из самых мощных функций языка, и это побудило меня поискать в github и Cargo, чтобы увидеть, что там есть. Ниже приведены несколько интересных макросов, которые не так хорошо известны.

1) Log-derive - Регистрация результатов

Https://crates.io/crates/log-derive

#[logfn(ok = "TRACE", Err = "Error", fmt = "Failed fetching json: {:?}")]
fn  fetch(url: &str) -> reqwest::Result<reqwest::Response> {
	reqwest::get(url)
}

Вот макрос для регистрации ошибок функции. Добавьте этот атрибут в функцию, возвращающую Result, и одной строкой вы включите регистрацию ошибок. Настройте уровень журнала и регистрируемое сообщение, прикрепленное к обоим вариантам Err или Ok перечисления.

Сгенерированный код использует ящик журнала https://crates.io/crates/log.

2) Резюме - разбор регулярного выражения

Https://crates.io/crates/recap

#[recap(regex = r#"(?x)
 (?P<foo>\d+)
 \s+
 (?P<bar>true|false)
 \s+
 (?P<baz>\S+)
 "#)]
struct LogEntry {
    foo: usize,
    bar: bool,
    baz: String,
}
let entry: LogEntry = "1 true hello".parse()?;

Recap - это простой способ построить данные из строк регулярных выражений. Каждый атрибут структуры соответствует группе в строке регулярного выражения, которая определена в теге атрибута. Затем макрос получает признак FromStr, который заполняет структуру данными после вызова метода синтаксического анализа для строки.

3) термоусадочные пленки - создавайте разные типы

Https://crates.io/crates/shrinkwraprs

#[derive(Shrinkwrap)]
struct Email(String);
let email = Email("[email protected]".into());
let is_discriminated_email =
    email.contains("+");  // Woohoo, we can use the email like a string!

Shrinkwraprs переопределяет тип данных как новый отдельный тип. Вы можете добавить атрибут Shrinkwrap, чтобы унаследовать все поведение встроенного типа данных. Полезно, если вы хотите использовать возможности средства проверки типов для проверки правильности кода. Используйте этот ящик для создания вариантов типа библиотеки, которые имеют одинаковое поведение и данные, но являются разными типами.

4) С измерением

Https://crates.io/crates/metered
Этот макрос автоматически генерирует следующую статистику для метода

  • HitCount: количество вызовов кода
  • ErrorCount: количество возвращенных ошибок - (работает с любым выражением, возвращающим Result)
  • InFlight: количество активных запросов
  • ResponseTime: статистика продолжительности выражения
  • Throughput: сколько раз выражение вызывается в секунду.
#[derive(Default, Debug, serde::Serialize)]
pub struct Biz {
    metrics: BizMetrics,
}
#[metered(registry = BizMetrics)]
impl Biz {
    #[measure([HitCount, Throughput])]
    pub fn biz(&self) {        
        let delay = std::time::Duration::from_millis(rand::random::<u64>() % 200);
        std::thread::sleep(delay);
    }   
}

В структуру вы добавите атрибут метрики, и макрос будет сгенерирован новым типом метрики. Например:
metrics: BizMetrics,

Затем имя атрибута должно соответствовать типу метрики
#[metered(registry = BizMetrics)]

Получите метрики как сериализованный yaml:

serde_yaml::to_string(&*biz).unwrap();
metrics:
  biz:
    hit_count: 1000
    throughput:
      - samples: 20
        min: 35
        max: 58
        mean: 49.75
        stdev: 5.146600819958742
        90%ile: 55
        95%ile: 55
        99%ile: 58
        99.9%ile: 58
        99.99%ile: 58
      - ~

5) Derive-new - генерировать конструкторы

Https://github.com/nrc/derive-new

#[derive(new)]
struct Foo {
    x: bool,
    #[new(value = "42")]
    y: i32,
    #[new(default)]
    z: Vec<String>,
}
let _ = Foo::new(true);

Derive-new - это простой способ добавить конструктор в структуру. Этот метод добавит метод impl fn new(...) -> Self, созданный из атрибутов структуры, который инициализирует структуру.

6) Snafu - управление ошибками

Https://crates.io/crates/snafu

Этот ящик имеет множество вспомогательных функций для работы с ошибками в Rust. Работа с ошибками в Rust может быть многословной, и наличие библиотеки для сокращения кода и повышения читабельности - это то, что вы можете искать после запуска проекта. Он имеет функции для генерации Fromtraits для ошибок, которые будут использоваться с оператором try (?), простые методы для встраивания данных, связанных с ошибками, в структуру ошибок и помощников для генерации сообщений об ошибках отображения. Это хорошо продуманный ящик с ошибками, включающий большую часть функций, которые понадобятся разработчику для управления ошибками в Rust.

#[derive(Debug, Snafu)]
enum Error {
    #[snafu(display("Could not open config from {}: {}", filename.display(), source))]
    OpenConfig { filename: PathBuf, source: std::io::Error },
    #[snafu(display("Could not save config to {}: {}", filename.display(), source))]
    SaveConfig { filename: PathBuf, source: std::io::Error },
    #[snafu(display("The user id {} is invalid", user_id))]
    UserIdInvalid { user_id: i32, backtrace: Backtrace },
}
type Result<T, E = Error> = std::result::Result<T, E>;
fn log_in_user<P>(config_root: P, user_id: i32) -> Result<bool>
where
    P: AsRef<Path>,
{
    let config_root = config_root.as_ref();
    let filename = &config_root.join("config.toml");
    let config = fs::read(filename).context(OpenConfig { filename })?;
    // Perform updates to config
    fs::write(filename, config).context(SaveConfig { filename })?;
    ensure!(user_id == 42, UserIdInvalid { user_id });
    Ok(true)
}

Метод content используется для встраивания соответствующей информации в ошибку.

Если перечисление имеет поле source, его селектор контекста будет иметь реализацию From для Context. Это позволяет разработчику использовать оператор try (?), и тип будет преобразован в перечисление Result.

fs::write(filename, config).context(SaveConfig { filename })?

В противном случае в селекторе контекста будет метод fail для создания перечисления Result.

LoadNextPage { page_number }.fail() 

Обо мне

Rust, биткойн-образование, участник StackOverflow. Подпишитесь на Twitter по адресу https://twitter.com/BenMcDonald___

📝 Прочтите этот рассказ позже в Журнале.

👩‍💻 Просыпайтесь каждое воскресное утро и слушайте самые интересные истории недели в области технологий, которые ждут вас в вашем почтовом ящике. Прочтите информационный бюллетень« Примечательно в технологиях .