Если говорить о паттернах и ООП, то нельзя не упомянуть одно из последних нововведений - процедурные макросы. Они позволяют существенно облегчить боль от отсутствия механизма наследования в Rust.
В книге есть пример как сделать свой типаж “наследуемым”, т.е. просто добавив в определение структуры атрибут #[derive(HelloMacro)]
мы получаем автоматическую реализацию типажа HelloMacro
.
Можно ввести дополнительные атрибуты, которые позволят указывать какие поля структуры используются в реализации.
Например, пусть функция hello_macro()
нашего типажа выводит поле name
:
#[derive(HelloMacro)]
struct Pancakes{
name:String,//field 'name' is displayed by default using HelloMacro
address:u64
}
Для структуры Pancakes
автоматически сгенерируется такой код:
impl HelloMacro for Pancakes {
fn hello_macro(&self) {
println!("Hello, {}!", self.name);
}
}
Чтобы реализовать HelloMacro
для структуры в которой нет поля name
, добавим собственный атрибут #[name]
, который скажет нашему макросу какое поле использовать в реализации типажа:
#[derive(HelloMacro)]
struct Cookies{
#[name]
xxx:&'static str,
yyy:u8
}
Автоматически сгенерированный код:
impl HelloMacro for Cookies{
fn hello_macro(&self) {
println!("Hello, {}!", self.xxx);
}
}
Все можно сделать очень красиво и с выводом своих ошибок в случае неправильного использования атрибутов. Код процедурных макросов должен быть расположен в отдельном крейте. Его нужно указать в разделе [dependencies]
в файле Cargo.toml
.
Сложность в том, что все граничные случаи и возможности применения кастомных атрибутов приходится прописывать самому. Заниматься этим имеет смысл если вы хотите сделать максимально простой в использовании API для своей библиотеки.
Вот немного допиленный код примера из книги:
Крейт pancakes
:
pancakes\src\main.rs
use hello_macro_derive::HelloMacro;
trait HelloMacro {
fn hello_macro(&self);
}
#[derive(HelloMacro)]
struct Pancakes{
name:String,//field 'name' is displayed by default using HelloMacro
address:u64
}
//#[derive(HelloMacro)]//no field `name` on type `&Dumplings`
struct Dumplings{
xxx:usize
}
#[derive(HelloMacro)]
struct Cookies{
#[name]
xxx:&'static str,
//#[name]//Attribute #[name] applied to multiple fields
yyy:u8
}
//#[derive(HelloMacro)]
struct Ravioli(
//#[name]//Attribute #[name] applied to unnamed field
usize
);
//#[derive(HelloMacro)]//Attribute #[derive(HelloMacro)] applied to something that is not a struct
enum Rabbit{}
fn main() {
Pancakes{ name:"World".to_owned(), address:0x80000001 }.hello_macro();
Cookies{ xxx:"Maria", yyy:1 }.hello_macro();
}
pancakes\Cargo.toml
[package]
name = "pancakes"
version = "0.1.0"
edition = "2018"
[dependencies]
hello_macro_derive = { path = "../hello_macro_derive" }
Крейт hello_macro_derive
:
hello_macro_derive\src\lib.rs
extern crate proc_macro;
extern crate proc_macro2;
use proc_macro::TokenStream;
use quote::quote;
use proc_macro2::{Ident, Span};
#[proc_macro_derive(HelloMacro, attributes(name))]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
impl_hello_macro(&ast)
}
fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
let defid = Ident::new("name", Span::call_site());
let name = &ast.ident;
let mut fieldid = None;
if let syn::Data::Struct(ref strct) = ast.data{
for field in strct.fields.iter(){
for attr in field.attrs.iter(){
if attr.path.is_ident("name"){
if fieldid.is_some() { panic!("Attribute #[name] applied to multiple fields"); }
fieldid = Some(field.ident.as_ref().expect("Attribute #[name] applied to unnamed field"));
}
}
}
}else{
panic!("Attribute #[derive(HelloMacro)] applied to something that is not a struct");
}
let fieldid = fieldid.unwrap_or(&defid);
let gen = quote! {
impl HelloMacro for #name {
fn hello_macro(&self) {
println!("Hello, {}!", self.#fieldid);
}
}
};
gen.into()
}
hello_macro_derive\Cargo.toml
[package]
name = "hello_macro_derive"
version = "0.1.0"
edition = "2018"
[lib]
proc-macro=true
[dependencies]
syn = "*"
quote = "*"
proc-macro2 = "*"