ã¯ããã« åç·š ã§ã¯ã setter ã¡ãœããã«ããå€ã®èšå®ã build ã¡ãœããã«ããæ§é äœã®çæãªã©ã®åºæ¬çãªæ©èœãæã£ãæç¶ãçãã¯ããå®è£
ããŸãããåŸç·šã§ã¯ä»¥äžã®æ©èœãå®è£
ããŠãããŸãã Optional ãªå€ãæ§é äœã®ãã£ãŒã«ããšããŠæãŠãããã«ãã 以äžã® 2 ã€ã®æ¹æ³ã§ Vec åã®ãã£ãŒã«ããæŽæ°ã§ããããã«ãã ãã¯ã¿ãäžããŠäžæ¬ã§æŽæ°ãã ãã¯ã¿ã®èŠçŽ ãäžã㊠1 ã€ãã€ãã£ãŒã«ãã«èŠçŽ ã远å ãã ã³ã³ãã€ã«ãšã©ãŒãçºçããéã«ããããããã¡ãã»ãŒãžã衚瀺ãã builder ãã¯ããäœãïŒç¶ãïŒ 06-optional-field ç®æš æ§é äœã®ãã£ãŒã«ããšã㊠Optional ãªå€ãæãŠãããã«ãã Optional ãªãã£ãŒã«ãã«ã¯å€ãå
¥ã£ãŠããªããŠã build ã¡ãœããã§æ§é äœãçæã§ãã Optional ã§ãªããã£ãŒã«ãã¯å€ãå
¥ã£ãŠããªããš build ã¡ãœããã§æ§é äœãçæã§ããªã Optional ãªãã£ãŒã«ã㯠Some ã§ã©ããããã«äžèº«ã®å€ããã®ãŸãŸäœ¿ã£ãŠåæåã§ãã æåŸã®é
ç®ã«ã€ããŠã§ãããããšãã° Command æ§é äœã以äžã®ããã«ãªã£ãŠããå Žåã pub struct Command { executable: String, args: Vec<String>, env: Vec<String>, current_dir: Option<String>, } current_dir ã¯ä»¥äžã®ããã« String ãæž¡ãã ãã§ãããšããããšã§ãã let command = Command::builder() .executable("cargo".to_owned()) .args(vec!["build".to_owned(), "--release".to_owned()]) .env(vec![]) .current_dir("..".to_owned()) .build() .unwrap(); å®è£
æ¹é ç®æšãšããæ©èœãå®çŸããããã«å®è£
ããå¿
èŠãããã®ã¯ä»¥äžã®é
ç®ã§ãããŸãã¯ãããã®æ©èœãå®è£
ããŠãããŸãããã ã¬ãŒãç¯ã§ Optional ã§ãªãåã®ã¿ãšã©ãŒãåºãããã«ãã Option ã§ã©ãããããåã¯ã¢ã³ã©ããã㊠CommandBuilder æ§é äœã®ãã£ãŒã«ãã§ä¿æãã ä»ã¯å
ã®åãäœã§ãã£ãŠã Option ã§ã©ããããããã«ãªã£ãŠããŸãããã®ãããOptional ãªå㯠Option<Option<_>> ã®ããã«ãªããŸãããã®ãŸãŸã ãšæ±ãã¥ããã®ã§ãOptional ãªåã¯ãã£ããã¢ã³ã©ããããŠããã¹ãŠã®ãã£ãŒã«ãã®åã Option<_> ã«ãªãããã«ããŸã Optional ãªåã® setter ã¡ãœããã¯ã©ãããããäžèº«ã®åãåŒæ°ãšããŠåãä»ããããã«ãã å®è£
å®è£
æ¹éã§èª¬æããæ©èœãå®è£
ããŠãããŸãã ã¬ãŒãç¯ã§ Optional ã§ãªãåã®ã¿ãšã©ãŒãåºãããã«ãã ã¬ãŒãç¯ãçæããéšåã®å®è£
ã¯ä»¥äžã®ããã«ãªã£ãŠããŸããä»ã¯ãã¹ãŠã®ãã£ãŒã«ãã«å¯Ÿã㊠None ã§ãããã®ãã§ãã¯ãçæããŠããŸããããã Optional ã§ãªãåã®ã¿ã¬ãŒãç¯ãçæããããã«å€æŽããŸãã let checks = idents.iter().map(|ident| { let err = format!("Required field '{}' is missing", ident.to_string()); quote! { if self.#ident.is_none() { return Err(#err.into()) } } }); Optional ã§ãªããã£ãŒã«ãã®ã¿ã¬ãŒãç¯ãçæããããã«ã¯ãåãã£ãŒã«ãã®åãèŠãŠ Optional ã§ãªããã£ãŒã«ãã®ã¿ããã£ã«ã¿ããã°è¯ãããã§ãã types ã«åãã£ãŒã«ãã®åãæ ŒçŽãããŠããã®ã§ã以äžã®ããã« filter ãçšã㊠Optional ã§ãªããã£ãŒã«ãã®èå¥åã«ã€ããŠã ãã¬ãŒãç¯ãçæããŸãã is_option ã¯äžããããåã Optional ã§ãããã©ãããå€å®ããäœããã®é¢æ°ã§ããã®ã¡ã»ã©å®è£
ããŸãã let checks = idents .iter() .zip(&types) .filter(|(_, ty)| !is_option(ty)) .map(|(ident, _)| { let err = format!("Required field '{}' is missing", ident.to_string()); quote! { if self.#ident.is_none() { return Err(#err.into()) } } }); Option ã§ã©ãããããåã¯ã¢ã³ã©ããã㊠CommandBuilder æ§é äœã®ãã£ãŒã«ãã§ä¿æãã ä»ã® Builder æ§é äœã®å®çŸ©ã¯ä»¥äžã®ããã«ãªã£ãŠããŸãããã¹ãŠã®ãã£ãŒã«ãã Option ã§ã©ããããŠããŸããOptional ãªãã£ãŒã«ãã«ã€ããŠã¯ãããããã Option ã§ã©ãããããåãåãåºããŠããã°ãããšã®åŠçã¯ä»ãŸã§ãšåãå
容ã«ãªããŸãã #vis struct #builder_name { #(#idents: Option<#types>),* } å®éã«å®è£
ããŠãããŸãã Option ã®äžèº«ã®åãåãåºããªã©ã®åŠçã远å ãããã®ã§ãçæããã Builder æ§é äœã®ãã£ãŒã«ãã builder_fields ã«ãã£ããä¿æããŠããšã§å±éããŸãããã builder_fields ã®å®è£
ã¯ä»¥äžã®ããã«ãªããŸãã unwrap_option 颿°ã§ Option ã«ã©ãããããŠããåãåãåºããŠãã以å€ã¯ä»ãŸã§ãšåãã§ãã unwrap_option 㯠is_option ãšåããã®ã¡ã»ã©å®è£
ããŸãã let builder_fields = idents.iter().zip(&types).map(|(ident, ty)| { let t = unwrap_option(ty).unwrap_or(ty); quote! { #ident: Option<#t> } }); builder_fields 㯠Builder æ§é äœã®å®çŸ©ãçæããéšåã§å±éããŸãã #vis struct #builder_name { #(#builder_fields),* } Builder æ§é äœã®å®çŸ©ãå€ãã£ããã build 颿°ãå€ããå¿
èŠããããŸããä»ã¯ä»¥äžã®ããã«ç®çã®æ§é äœãçæããéã«ãã¹ãŠã®ãã£ãŒã«ãã unwrap ããŠããŸãããOptional ãªãã£ãŒã«ã㯠unwrap ããå¿
èŠããªãã®ã§ãã®ãŸãŸè¿ãããã«ããŸãããã pub fn build(&mut self) -> Result<#ident, Box<dyn std::error::Error>> { #(#checks)* Ok(#ident { #(#idents: self.#idents.clone().unwrap()),* }) } Optional ãªãã£ãŒã«ããã©ãããå€å®ããåŠçã远å ãããããã builder_fields ãšåæ§ã«å¥ã®å€æ°ã«æ ŒçŽããŠã®ã¡ã»ã©å±éããŸããå
·äœçãªå®è£
ã¯ä»¥äžã®éãã§ãã is_option ã§ Optional ãªãã£ãŒã«ããã©ãããå€å®ããŠãOptional ã§ããã°å€ããã®ãŸãŸäœ¿çšããOptional ã§ãªããã° unwrap ããŠåŸãããå€ã䜿çšããŸãã let struct_fields = idents.iter().zip(&types).map(|(ident, ty)| { if is_option(ty) { quote! { #ident: self.#ident.clone() } } else { quote! { #ident: self.#ident.clone().unwrap() } } }); struct_fields 㯠build 颿°ã®äžã§å±éããŸãã pub fn build(&mut self) -> Result<#ident, Box<dyn std::error::Error>> { #(#checks)* Ok(#ident { #(#struct_fields),* }) } Optional ãªåã® setter ã¡ãœããã¯ã©ãããããäžèº«ã®åãåŒæ°ãšããŠåãä»ããããã«ãã ä»ã® setter ã®å®è£
ã¯ä»¥äžã®ããã«ãªã£ãŠããŸããOptional ãªãã£ãŒã«ããã©ããã¯é¢ä¿ãªãããã¹ãŠã®ãã£ãŒã«ãã«ã€ããŠãã®ãã£ãŒã«ãã®åããã®ãŸãŸåãä»ããããã«ãªã£ãŠããŸãã impl #builder_name { #(pub fn #idents(&mut self, #idents: #types) -> &mut Self { self.#idents = Some(#idents); self })* ... ã€ãŸã Optional ãªãã£ãŒã«ãã«ã€ããŠã¯ä»¥äžã®ãã㪠setter ãçæãããŸããããã§ã¯ Builder æ§é äœã®ãã£ãŒã«ãå®çŸ©ãšççŸããŸãããã®ãããOptional ãªãã£ãŒã«ãã® setter 颿°ã¯åŒæ°ãšã㊠Option ã®äžèº«ã®åãåãåãããã«ããŸãã pub fn current_dir(&mut self, current_dir: Option<String>) -> &mut Self { self.current_dir = Some(current_dir); self } å
·äœçã«ã¯ä»¥äžã®ãã㪠setter ãäœã£ãŠå±éããããã«æžãæããŸããBuilder æ§é äœã®ãã£ãŒã«ããšåæ§ã unwrap_option 颿°ã䜿ã£ãŠ Option ã§ã©ãããããäžèº«ã®åãåãåºããŸãã let setters = idents.iter().zip(&types).map(|(ident, ty)| { let t = unwrap_option(ty).unwrap_or(ty); quote! { pub fn #ident(&mut self, #ident: #t) -> &mut Self { self.#ident = Some(#ident); self } } }); ... impl #builder_name { #(#setters)* ... is_option ãš unwrap_option ã®å®è£
is_option ãš unwrap_option ãå®è£
ããŠãããŸãããŸã㯠is_option 颿°ããå®è£
ããŠãããŸãããã is_option 颿° is_option 㯠Type åãåãåã£ãŠããã Option ãã©ãããå€å®ãã颿°ãªã®ã§ã·ã°ããã£ã¯ä»¥äžã®ããã«ããã°è¯ãããã§ãã fn is_option(ty: &Type) -> bool ty ã Option ãã©ããå€å®ããã®ã«äœ¿ããã㪠Type ã®ã¡ãœãããããã確èªããŠã¿ãŸãããã syn ã¯ã¬ãŒãã®ããã¥ã¡ã³ã ã確èªãããšãã Type ã¯ä»¥äžã® enum ã®ããã§ãã pub enum Type { Array(TypeArray), BareFn(TypeBareFn), Group(TypeGroup), ImplTrait(TypeImplTrait), Infer(TypeInfer), Macro(TypeMacro), Never(TypeNever), Paren(TypeParen), Path(TypePath), Ptr(TypePtr), Reference(TypeReference), Slice(TypeSlice), TraitObject(TypeTraitObject), Tuple(TypeTuple), Verbatim(TokenStream), // some variants omitted } Option ãã©ã®ããªã¢ã³ãã«åé¡ããããã¯ãŸã ããããŸãããããšãããããã¿ãŒã³ãããã§åŠçããã°è¯ãããã§ãã fn is_option(ty: &Type) -> bool { match ty { todo!() } } ãã¿ãŒã³ãããã§åé¡ã§ãããã ãšãããšãããŸã§æ¹éãç«ãŠãããŸãããã Option ã¯ã©ã®ããªã¢ã³ãã«åé¡ãããã®ã§ãããããããã¥ã¡ã³ããäžèŠããŠãããããããã®ã¯èŠåœãããŸãããã Option 㯠Type::Path(syn::TypePath) ã«åé¡ãããŸãã TypePath 㯠std::iter::Iter ã®ãã㪠Path ãããŒã¹ããŠåŸãããæ§é äœã§ãã Type::Path ã«ãããããªãããªã¢ã³ãã«ã€ããŠã¯ãã®æç¹ã§ false ãè¿ããŠããŸã£ãŠåé¡ãªãã§ãããã fn is_option(ty: &Type) -> bool { match ty { Type::Path(path) => todo!(), _ => false } } TypePath ã«ã¯ Option 以å€ã«ãäžè¿°ã® std::iter::Iter ã®ãããªãã®ãå«ãŸããŸããã©ã®ããã« Option ããã以å€ããå€å®ããã°è¯ãã§ããããã å
ã»ã©ã説æããããã«ã TypePath 㯠std::iter::Iter ã®ããã«ã³ãã³ 2 ã€ã§åå²ãããã»ã°ã¡ã³ãã®éåã§ãããã€ãŸããã»ã°ã¡ã³ãã®éåã®æåŸã®èŠçŽ ã Option ã§ãããã©ããã倿ããã°è¯ãããã§ãã ã§ã¯ãã©ã®ããã«ããŠã»ã°ã¡ã³ãã®éåã®æåŸã®èŠçŽ ãååŸããã°è¯ãã®ã§ããããã syn::TypePath ã¯ä»¥äžã®ãããªæ§é äœã§ã path ã« Path ã®æ
å ±ãä¿æããŠããŸãã syn::Path ã¯ä»¥äžã®ããã« segments ã«ã»ã°ã¡ã³ãã®éåãä¿æããŠããã segments.last() ã§æåŸã®èŠçŽ ã«ã¢ã¯ã»ã¹ã§ããŸãã pub struct TypePath { pub qself: Option<QSelf>, pub path: Path } pub struct Path { pub leading_colon: Option<Colon2>, pub segments: Punctuated<PathSegment, Colon2>, } äžèšãèžãŸãããšãçŸæç¹ã§ã®å®è£
ã¯ä»¥äžã®ããã«ãªããŸãã fn is_option(ty: &Type) -> bool { match ty { Type::Path(path) => path.path.segments.last(), _ => false } } æåŸã«ãã»ã°ã¡ã³ãã®æåŸã®èŠçŽ ã®èå¥åã Option ãšäžèŽãããæ¯èŒããå¿
èŠããããŸãã segments.last() ã®æ»ãå€ã¯ PathSegment å ã§ããã ident ãã£ãŒã«ãããèå¥åã«ã¢ã¯ã»ã¹ã§ããŸãããã®èå¥åã Option ãã©ãããæ¯èŒããã°è¯ãããã§ãã æçµç㪠is_option 颿°ã®å®è£
ã¯ä»¥äžã®ããã«ãªããŸãã fn is_option(ty: &Type) -> bool { match ty { Type::Path(path) => match path.path.segments.last().unwrap() { Some(seg) => seg.ident == "Option", None => false }, _ => false } } unwrap_option 颿° 次㯠unwrap_option 颿°ãå®è£
ããŠããŸãã unwrap_option 颿°ã¯äžããããåã Option ã§ããã° Some(å) ãè¿ããããã§ãªããã° None ãè¿ã颿°ãªã®ã§ãã·ã°ããã£ã¯ä»¥äžã®ããã«ããã°è¯ãã§ãããã fn unwrap_option(ty: &Type) -> Option<&Type> åŒæ°ã Option ã§ãªãå Žå㯠None ãè¿ããŸããåŒæ°ã Option ãã©ããã¯å
ã»ã©å®è£
ãã is_option 颿°ã䜿ããŸãã fn unwrap_option(ty: &Type) -> Option<&Type> { if !is_option(ty) { return None; } todo!() } 次㫠Option ã«ã©ãããããäžèº«ã®åãåãåºãåŠçãå®è£
ããŸããäžèº«ã®åã¯ã©ããã£ãŠåãåºãã°è¯ãã§ããããã is_option ã®å®è£
ã§ã§ãŠãã PathSegment ã®ãã£ãŒã«ãã«ã¯ ident 以å€ã«ãã 1 〠arguments ãšããã®ããããŸããããããé¢ä¿ãããããªã®ã§ã PathArguments ã®å®çŸ©ãèŠãŠèŠãŸãããã pub enum PathArguments { None, AngleBracketed(AngleBracketedGenericArguments), Parenthesized(ParenthesizedGenericArguments), } PathArguments 㯠enum ã®ããã§ããããªã¢ã³ãåãçºããŠã¿ããš AngleBracketed ãšãããã®ããããŸãããããé¢ä¿ããããã§ããå®éã ããã¥ã¡ã³ã ã® AngleBracketed ã®é
ç®ã«ã¯ä»¥äžã®ããã«èšèŒãããŠããããã®ããªã¢ã³ãããžã§ããªãã¯åŒæ°ã®æ
å ±ãæã£ãŠããããšãããããŸãã AngleBracketed(AngleBracketedGenericArguments) The <'a, T> in std::slice::iter<'a, T> . syn::AngleBracketedGenericArgument åã®å®çŸ©ãèŠãŠã¿ãŸãããã pub struct AngleBracketedGenericArguments { pub colon2_token: Option<Colon2>, pub lt_token: Lt, pub args: Punctuated<GenericArgument, Comma>, pub gt_token: Gt, } ãã£ãŒã«ãåãçºããŠã¿ããšãžã§ããªãã¯åŒæ°ã®å㯠args ã«å
¥ã£ãŠãããã§ãã syn::Punctuated ãªã®ã§è€æ°ã®èŠçŽ ãããããã§ããã Option ã¯åŒæ°ã 1 ã€ããåããªãã®ã§ first() ã§ååŸããã°è¯ãã§ãããã fn unwrap_option(ty: &Type) -> Option<&Type> { if !is_option(ty) { return None; } match ty { Type::Path(path) => path.path.segments.last().map(|seg| { match seg.arguments { PathArguments::AngleBracketed(ref args) => args.args.first(), _ => None } }), _ => None } } args.first() ã®æ»ãå€ã¯ Option<GenericArgument> ã§ãã GenericArgument ã¯ä»¥äžã®ãã㪠enum ã§ãåãå«ãŸããå Žå㯠GenericArgument::Type ããªã¢ã³ãã䜿çšãããã®ã§ã Type ããªã¢ã³ãã«ããããããã°è¯ãã§ãããã pub enum GenericArgument { Lifetime(Lifetime), Type(Type), Binding(Binding), Constraint(Constraint), Const(Expr), } äžèšãèžãŸãããšã unwrap_option ã®æçµçãªå®è£
ã¯ä»¥äžã®ããã«ãªããŸãã fn unwrap_option(ty: &Type) -> Option<&Type> { if !is_option(ty) { return None; } match ty { Type::Path(path) => path.path.segments.last().map(|seg| { match seg.arguments { PathArguments::AngleBracketed(ref args) => { args.args.first().and_then(|arg| match arg { &GenericArgument::Type(ref ty) => Some(ty), _ => None, }) } _ => None } }), _ => None } } Path ã®æåŸã®èŠçŽ ãæã£ãŠããåŠçã¯ãŸãšããããã®ã§å¥ã®é¢æ°ãšããŠå€ã«åãåºããŸãããããçšã㊠is_option ãš unwrap_option ãæžãçŽããšãæçµçã«ã¯ä»¥äžã®ããã«ãªããŸãã fn get_last_path_segment(ty: &Type) -> Option<&PathSegment> { match ty { Type::Path(path) => path.path.segments.last(), _ => None, } } fn is_option(ty: &Type) -> bool { match get_last_path_segment(ty) { Some(seg) => seg.ident == "Option", _ => false, } } fn unwrap_option(ty: &Type) -> Option<&Type> { if !is_option(ty) { return None; } match get_last_path_segment(ty) { Some(seg) => match seg.arguments { PathArguments::AngleBracketed(ref args) => { args.args.first().and_then(|arg| match arg { &GenericArgument::Type(ref ty) => Some(ty), _ => None, }) } _ => None, }, None => None, } } æçµçãªå®è£
ã¯ä»¥äžã®ããã«ãªããŸãã use proc_macro::TokenStream; use quote::{format_ident, quote}; use syn::{ parse_macro_input, Data, DeriveInput, Fields, GenericArgument, Ident, PathArguments, PathSegment, Type, }; #[proc_macro_derive(Builder)] pub fn derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let ident = input.ident; let vis = input.vis; let builder_name = format_ident!("{}Builder", ident); let (idents, types): (Vec<Ident>, Vec<Type>) = match input.data { Data::Struct(data) => match data.fields { Fields::Named(fields) => fields .named .into_iter() .map(|field| { let ident = field.ident; let ty = field.ty; (ident.unwrap(), ty) }) .unzip(), _ => panic!("no unnamed fields are allowed"), }, _ => panic!("expects struct"), }; let builder_fields = idents.iter().zip(&types).map(|(ident, ty)| { let t = unwrap_option(ty).unwrap_or(ty); quote! { #ident: Option<#t> } }); let checks = idents .iter() .zip(&types) .filter(|(_, ty)| !is_option(ty)) .map(|(ident, _)| { let err = format!("Required field '{}' is missing", ident.to_string()); quote! { if self.#ident.is_none() { return Err(#err.into()) } } }); let setters = idents.iter().zip(&types).map(|(ident, ty)| { let t = unwrap_option(ty).unwrap_or(ty); quote! { pub fn #ident(&mut self, #ident: #t) -> &mut Self { self.#ident = Some(#ident); self } } }); let struct_fields = idents.iter().zip(&types).map(|(ident, ty)| { if is_option(ty) { quote! { #ident: self.#ident.clone() } } else { quote! { #ident: self.#ident.clone().unwrap() } } }); let expand = quote! { #vis struct #builder_name { #(#builder_fields),* } impl #builder_name { #(#setters)* pub fn build(&mut self) -> Result<#ident, Box<dyn std::error::Error>> { #(#checks)* Ok(#ident { #(#struct_fields),* }) } } impl #ident { pub fn builder() -> #builder_name { #builder_name { #(#idents: None),* } } } }; proc_macro::TokenStream::from(expand) } fn is_option(ty: &Type) -> bool { match get_last_path_segment(ty) { Some(seg) => seg.ident == "Option", _ => false, } } fn unwrap_option(ty: &Type) -> Option<&Type> { if !is_option(ty) { return None; } match get_last_path_segment(ty) { Some(seg) => match seg.arguments { PathArguments::AngleBracketed(ref args) => { args.args.first().and_then(|arg| match arg { &GenericArgument::Type(ref ty) => Some(ty), _ => None, }) } _ => None, }, None => None, } } fn get_last_path_segment(ty: &Type) -> Option<&PathSegment> { match ty { Type::Path(path) => path.path.segments.last(), _ => None, } } ãªãã¡ã¯ã¿ãªã³ã° derive 颿°ãè¥å€§åããŠããã®ã§å
éšã®åŠçã颿°ãšããŠåãåºããŸãããäž»ãªå€æŽç¹ã¯ä»¥äžã®éãã§ãã Builder æ§é äœã®å®çŸ©ãçæããéšåã颿°åïŒ build_builder_struct ïŒ Builder æ§é äœã®å®è£
ïŒsetter 颿°ã build 颿°ïŒãçæããéšåã颿°åïŒ build_builder_impl ïŒ builder 颿°ãçæããéšåã颿°åïŒ build_struct_impl ïŒ ãããã®é¢æ°ã¯ãã¹ãŠæ»ãå€ãšã㊠proc_macro2::TokenStream ãè¿ããŠããŸããããã㯠quote ãã¯ãã proc_macro2::TokenStream ãè¿ãããã§ãã ãããã®åŠçã颿°åããã®ã«äŒŽããããšããšä»¥äžã®ããã«ãã£ãŒã«ãåãšåãæåã«ååŸããŠããã®ãã let (idents, types): (Vec<Ident>, Vec<Type>) = match input.data { Data::Struct(data) => match data.fields { Fields::Named(fields) => fields .named .into_iter() .map(|field| { let ident = field.ident; let ty = field.ty; (ident.unwrap(), ty) }) .unzip(), _ => panic!("no unnamed fields are allowed"), }, _ => panic!("expects struct"), }; 以äžã®ããã« NamedFields ãååŸããŠå颿°ã«ãããããã«å€æŽããŠããŸãã let fields = match input.data { Data::Struct(data) => match data.fields { Fields::Named(fields) => fields, _ => panic!("no unnamed fields are allowed"), }, _ => panic!("this macro can be applied only to structaa"), }; ãªãã¡ã¯ã¿ãªã³ã°åŸã®å®è£
ã¯ä»¥äžã®éãã§ãã use proc_macro2::TokenStream; use quote::{format_ident, quote}; use syn::{ parse_macro_input, Data, DeriveInput, Fields, FieldsNamed, GenericArgument, Ident, PathArguments, PathSegment, Type, Visibility, }; #[proc_macro_derive(Builder, attributes(builder))] pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); let ident = input.ident; let vis = input.vis; let builder_name = format_ident!("{}Builder", ident); let fields = match input.data { Data::Struct(data) => match data.fields { Fields::Named(fields) => fields, _ => panic!("no unnamed fields are allowed"), }, _ => panic!("this macro can be applied only to structaa"), }; let builder_struct = build_builder_struct(&fields, &builder_name, &vis); let builder_impl = build_builder_impl(&fields, &builder_name, &ident); let struct_impl = build_struct_impl(&fields, &builder_name, &ident); let expand = quote! { #builder_struct #builder_impl #struct_impl }; proc_macro::TokenStream::from(expand) } fn build_builder_struct( fields: &FieldsNamed, builder_name: &Ident, visibility: &Visibility, ) -> TokenStream { let (idents, types): (Vec<&Ident>, Vec<&Type>) = fields .named .iter() .map(|field| { let ident = field.ident.as_ref(); let ty = unwrap_option(&field.ty).unwrap_or(&field.ty); (ident.unwrap(), ty) }) .unzip(); quote! { #visibility struct #builder_name { #(#idents: Option<#types>),* } } } fn build_builder_impl( fields: &FieldsNamed, builder_name: &Ident, struct_name: &Ident, ) -> TokenStream { let checks = fields .named .iter() .filter(|field| !is_option(&field.ty)) .map(|field| { let ident = field.ident.as_ref(); let err = format!("Required field '{}' is missing", ident.unwrap().to_string()); quote! { if self.#ident.is_none() { return Err(#err.into()); } } }); let setters = fields.named.iter().map(|field| { let ident = &field.ident; let ty = unwrap_option(&field.ty).unwrap_or(&field.ty); quote! { pub fn #ident(&mut self, #ident: #ty) -> &mut Self { self.#ident = Some(#ident); self } } }); let struct_fields = fields.named.iter().map(|field| { let ident = field.ident.as_ref(); if is_option(&field.ty) { quote! { #ident: self.#ident.clone() } } else { quote! { #ident: self.#ident.clone().unwrap() } } }); quote! { impl #builder_name { #(#setters)* pub fn build(&mut self) -> Result<#struct_name, Box<dyn std::error::Error>> { #(#checks)* Ok(#struct_name { #(#struct_fields),* }) } } } } fn build_struct_impl( fields: &FieldsNamed, builder_name: &Ident, struct_name: &Ident, ) -> TokenStream { let field_defaults = fields.named.iter().map(|field| { let ident = field.ident.as_ref(); quote! { #ident: None } }); quote! { impl #struct_name { pub fn builder() -> #builder_name { #builder_name { #(#field_defaults),* } } } } } fn is_option(ty: &Type) -> bool { match get_last_path_segment(ty) { Some(seg) => seg.ident == "Option", _ => false, } } fn unwrap_option(ty: &Type) -> Option<&Type> { if !is_option(ty) { return None; } match get_last_path_segment(ty) { Some(seg) => match seg.arguments { PathArguments::AngleBracketed(ref args) => { args.args.first().and_then(|arg| match arg { &GenericArgument::Type(ref ty) => Some(ty), _ => None, }) } _ => None, }, None => None, } } fn get_last_path_segment(ty: &Type) -> Option<&PathSegment> { match ty { Type::Path(path) => path.path.segments.last(), _ => None, } } 07-repeated-field ç®æš 以äžã® 2 ã€ã®æ¹æ³ã§ãã¯ã¿ãå€ãšããŠãã€ãã£ãŒã«ããæŽæ°ã§ããããã«ãã ãã¯ã¿ãäžããŠäžæ¬ã§æŽæ°ãã ãã¯ã¿ã®èŠçŽ ã 1 ã€ãã€è¿œå ãã äžæ¬ã§æŽæ°ããããã®é¢æ°åã¯ãã£ãŒã«ãåãšåãã«ãã ãã¯ã¿ã®èŠçŽ ã 1 ã€ãã€è¿œå ãã颿°ã®ååã¯ä»¥äžã®ããã«ã¢ããªãã¥ãŒããçšããŠæå®ãã 1 ã€ãã€è¿œå ããããã®é¢æ°åãšããŠäžæ¬ã§æŽæ°ããããã®é¢æ°åãšåãååãæå®ãããå Žå㯠1 ã€ãã€è¿œå ããããã®é¢æ°ãåªå
ãã #[derive(Builder)] pub struct Command { executable: String, #[builder(each = "arg")] args: Vec<String>, #[builder(each = "env")] env: Vec<String>, current_dir: Option<String>, } å®è£
æ¹é ç®æšãšããæ©èœãå®çŸããããã«å®è£
ããå¿
èŠãããã®ã¯ä»¥äžã®é
ç®ã§ãã ãã£ãŒã«ãã«ä»äžãããã¢ããªãã¥ãŒããååŸãã builder ã¢ããªãã¥ãŒããä»äžã§ããããã«ãã èŠçŽ ã 1 ã€ãã€è¿œå ãã颿°å㯠build ã¢ããªãã¥ãŒãã® each ããŒã«æå®ãã ã¢ããªãã¥ãŒãã®ããŒåïŒ each ïŒã®ããªããŒã·ã§ã³ã¯æ¬¡ã®ã¹ãããã§å®è£
ãã Vec åã®ãã£ãŒã«ãã® setter ãäžæ¬æŽæ°çšãšèŠçŽ è¿œå çšã® 2 çš®é¡çæãã ãŸããèŠçŽ ã 1 ã€ãã€è¿œå ã§ããããã«ããããã«ã¯ä»¥äžã®æ©èœã®å®è£
ãå¿
èŠã§ãã Builder æ§é äœã®ãã£ãŒã«ãã§ã¯ Vec åã®å€æ°ã¯ Option ã§ã©ããããªã ãã£ãŒã«ãã®åã Vec ãã©ããå€å®ã§ããããã«ãã Vec ãã¢ã³ã©ããããŠäžèº«ã®åãååŸã§ããããã«ãã å®è£
Builder æ§é äœã®ãã£ãŒã«ãã§ã¯ Vec åã®å€æ°ã¯ Option ã§ã©ããããªã Builder æ§é äœã®å®çŸ©ãçæããŠããã®ã¯ build_builder_struct 颿°ã§ããä»ã®å®è£
ã§ã¯å
¥åã®åã«é¢ããã Option ã§ã©ããããŠããŸããããã Vec ã®ã¿ã©ããããªãããã«å€æŽããã°è¯ãããã§ãã fn build_builder_struct( fields: &FieldsNamed, builder_name: &Ident, visibility: &Visibility, ) -> TokenStream { let (idents, types): (Vec<&Ident>, Vec<&Type>) = fields .named .iter() .map(|field| { let ident = field.ident.as_ref(); let ty = unwrap_option(&field.ty).unwrap_or(&field.ty); (ident.unwrap(), ty) }) .unzip(); quote! { #visibility struct #builder_name { #(#idents: Option<#types>),* } } } is_vector 颿°ã¯ is_option ãšäŒŒã颿°ã§ãäžããããåã Vec åãã©ãããå€å®ãã颿°ã§ããå®è£
ã¯åŸè¿°ããŸãã fn build_builder_struct( fields: &FieldsNamed, builder_name: &Ident, visibility: &Visibility, ) -> TokenStream { let struct_fields = fields .named .iter() .map(|field| { let ident = field.ident.as_ref(); let ty = unwrap_option(&field.ty).unwrap_or(&field.ty); (ident.unwrap(), ty) }) .map(|(ident, ty)| { if is_vector(&ty) { quote! { #ident: #ty } } else { quote! { #ident: Option<#ty> } } }); quote! { #visibility struct #builder_name { #(#struct_fields),* } } } Builder æ§é äœã®å®çŸ©ãå€ãã£ãã®ã§ãBuilder æ§é äœãè¿ã builder 颿°ã®å®è£
ãçæãã build_struct_impl 颿°ãä¿®æ£ãå¿
èŠã§ãã Vec åã®ãã£ãŒã«ãã®ã¿ Vec::new() ãè¿ãããã«ããã°è¯ãããã§ãã fn build_struct_impl( fields: &FieldsNamed, builder_name: &Ident, struct_name: &Ident, ) -> TokenStream { let field_defaults = fields.named.iter().map(|field| { let ident = field.ident.as_ref(); quote! { #ident: None } }); quote! { impl #struct_name { pub fn builder() -> #builder_name { #builder_name { #(#field_defaults),* } } } } } ãã¡ãã build_builder_struct 颿°ãšåæ§ã is_vector 颿°ã䜿ã£ãŠæ¡ä»¶åå²ãèšè¿°ããŠããŸãã fn build_struct_impl( fields: &FieldsNamed, builder_name: &Ident, struct_name: &Ident, ) -> TokenStream { let field_defaults = fields.named.iter().map(|field| { let ident = field.ident.as_ref(); let ty = &field.ty; if is_vector(&ty) { quote! { #ident: Vec::new() } } else { quote! { #ident: None } } }); quote! { impl #struct_name { pub fn builder() -> #builder_name { #builder_name { #(#field_defaults),* } } } } } ãŸãã Vec åã®ãã£ãŒã«ãã¯èŠçŽ ãå«ãŸãªããŠãåé¡ãªãã®ã§ã Vec åã®ãã£ãŒã«ãã«ã€ããŠãã¬ãŒãç¯ãçæããªãããã«å€æŽããŸããä»ã¯ Option ã®ã¿ããã£ã«ã¿ããŠããŸããã远å ã§ Vec ããã£ã«ã¿ããããã«å€æŽããŸãã let checks = fields .named .iter() .filter(|field| !is_option(&field.ty)) .filter(|field| !is_vector(&field.ty)) .map(|field| { let ident = field.ident.as_ref(); let err = format!("Required field '{}' is missing", ident.unwrap().to_string()); quote! { if self.#ident.is_none() { return Err(#err.into()); } } }); is_vector ãš unwrap_vector ã®å®è£
ãããã㯠is_vector ãš unwrap_vector ãå®è£
ããŠãããŸãããããŸã§ã®å®è£
ã§ã¯ unwrap_vector ã¯ã§ãŠããŸããããä»åŸäœ¿ãã®ã§ããã§å®è£
ããŠãããŸãã Vec ã Option ãšåæ§ Type::Path ã«åé¡ãããã®ã§ã以äžã®é
ç®ã¯ 06 ã§å®è£
ãã is_option ã unwrap_option ãæµçšã§ããŸãã fn is_vector(ty: &Type) -> bool { match get_last_path_segment(ty) { Some(seg) => seg.ident == "Vec", _ => false, } } fn unwrap_vector(ty: &Type) -> Option<&Type> { if !is_vector(ty) { return None; } match get_last_path_segment(ty) { Some(seg) => match seg.arguments { PathArguments::AngleBracketed(ref args) => { args.args.first().and_then(|arg| match arg { &GenericArgument::Type(ref ty) => Some(ty), _ => None, }) } _ => None, }, None => None, } } unwrap_vector ã®ãã¿ãŒã³ãããã®éšå㯠unwrap_option ãšåæ§ã®åŠçãããŠããã®ã§é¢æ°åã§ãããã§ããããã unwrap_generic_type ãšãã颿°ã«ãããåºããšä»¥äžã®ããã«ãªããŸãã fn unwrap_option(ty: &Type) -> Option<&Type> { if !is_option(ty) { return None; } unwrap_generic_type(ty) } fn unwrap_vector(ty: &Type) -> Option<&Type> { if !is_vector(ty) { return None; } unwrap_generic_type(ty) } fn unwrap_generic_type(ty: &Type) -> Option<&Type> { match get_last_path_segment(ty) { Some(seg) => match seg.arguments { PathArguments::AngleBracketed(ref args) => { args.args.first().and_then(|arg| match arg { &GenericArgument::Type(ref ty) => Some(ty), _ => None, }) } _ => None, }, None => None, } } ãã£ãŒã«ãã«ä»äžãããã¢ããªãã¥ãŒããååŸãã ãã£ãŒã«ãã«ä»äžãããã¢ããªãã¥ãŒããååŸããåŠçãå®è£
ããåã«ããŸãã¢ããªãã¥ãŒããä»äžã§ããããã«ããå¿
èŠããããŸãã ã¢ããªãã¥ãŒããä»äžã§ããããã«ããããã«ã¯ã以äžã®ããã« derive 颿°ã« attributes(builder) ãšããã¢ããªãã¥ãŒãã远å ããŸãïŒ åè ïŒãããã§ãã£ãŒã«ãã« #[builder(...)] ã®ãããªã¢ããªãã¥ãŒããä»äžã§ããŸãã #[proc_macro_derive(Builder, attributes(builder))] pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { ã¢ããªãã¥ãŒããä»äžã§ããããã«ãªã£ãã®ã§ããããååŸããåŠçãå®è£
ããŸãã ã¢ããªãã¥ãŒããååŸããã«ã¯ã©ã®ããŒã¿ãåŠçããã°è¯ãã§ããããããŸã㯠DeriveInput ãã¿ãŠã¿ãŸãããã pub struct DeriveInput { pub attrs: Vec<Attribute>, pub vis: Visibility, pub ident: Ident, pub generics: Generics, pub data: Data, } DeriveInput 㯠attrs ãã£ãŒã«ããæã£ãŠããŸããã以äžã® DeriveInput ã® attrs ã®èª¬æã«ããããã«ãããã¯æ§é äœèªäœã«ä»äžãããã¢ããªãã¥ãŒãã§ããïŒ åŒçšå
ïŒ Attributes tagged on the whole struct or enum. æ§é äœã®ãã£ãŒã«ã㯠data ãã£ãŒã«ãã«æ ŒçŽãããŠããã®ã§ Data ã®å®çŸ©ãã¿ãŠã¿ãŸãããã Data ã®æ§é ãäžã£ãŠãããšæçµçã«æ§é äœã®åãã£ãŒã«ãã®æ
å ±ãä¿æããŠãã Field æ§é äœãåŸãããŸãã Data ã®æ§é ã®è©³çްã«ã€ããŠã¯ åç·š ãåèã«ããŠãã ããããã®äžã«ãã attrs ããã£ãŒã«ãã«ä»äžãããã¢ããªãã¥ãŒãã§ãã pub struct Field { pub attrs: Vec<Attribute>, pub vis: Visibility, pub ident: Option<Ident>, pub colon_token: Option<Colon>, pub ty: Type, } æ§é äœã®ãã£ãŒã«ã㯠derive 颿°ã®æåã®æ¹ã§ååŸããŠããã®ã§ããããåŠçããŠã¢ããªãã¥ãŒããååŸããŠãããŸãã attrs 㯠Attribute ã®ãã¯ã¿ã«ãªã£ãŠããŸãããä»å㯠1 ã€ã®ã¢ããªãã¥ãŒããã䜿ããªãã®ã§ first ã§å
é ã®ã¢ããªãã¥ãŒãã ãååŸããã°è¯ãã§ãããã let ident_each_name = field .attrs .first() .map(|attr| todo!()); Attribute ã® parse_meta 颿° ã§ã¢ããªãã¥ãŒããããŒã¹ããçµæãåŸãããŸãã let ident_each_name = field .attrs .first() .map(|attr| attr.parse_meta()); parse_meta() 颿°ã®æ»ãå€ã¯ Result<Meta> ã§ãã Meta åã¯ä»¥äžã®ãã㪠enum ã§ãã pub enum Meta { Path(Path), List(MetaList), NameValue(MetaNameValue), } Meta ã® List ããªã¢ã³ã㮠説æ ã« List A meta list is like the derive(Copy) in #[derive(Copy)] . ãšããããã«ãä»åååŸãããã®ã¯ Meta::List ãªã®ã§ãã¿ãŒã³ãããã§åŠçããŸãã let ident_each_name = field .attrs .first() .map(|attr| match attr.parse_meta() { Ok(Meta::List(_)) => todo!(), _ => None, }); MetaList ã¯ä»¥äžã®ãããªæ§é äœã§ãã pub struct MetaList { pub path: Path, pub paren_token: Paren, pub nested: Punctuated<NestedMeta, Comma>, } MetaList åã® path ã¯ã¢ããªãã¥ãŒãåïŒ #[builder(each="foo")] ã® builder ã®éšåïŒãã nested ã¢ããªãã¥ãŒãã®å€ïŒ #[builder(each="foo")] ã® each="foo" ã®éšåïŒãä¿æããŠããŸããä»å¿
èŠãªã®ã¯ã¢ããªãã¥ãŒãã®å€ãä¿æãã nested ã®éšåã§ãã nested ã¯ã¢ããªãã¥ãŒãå€ãè€æ°ä¿æããŠããŸãããä»åã¯è€æ°ã®å€ããã€ããšã¯æ³å®ããŠããªãã®ã§ first ã§å
é ãååŸããã°è¯ãããã§ãã let ident_each_name = field .attrs .first() .map(|attr| match attr.parse_meta() { Ok(Meta::List(list)) => list.nested.first(), _ => None, }); nested.first() 㯠Option<NestedMeta> ãè¿ããŸãã NestedMeta ã¯ä»¥äžã®ãã㪠enum ã§ãã pub enum NestedMeta { Meta(Meta), Lit(Lit), } NestedMeta ã®ãã£ãŒã«ãã«é¢ãã 以äžã®èšè¿° ãããããããã«ã Lit 㯠Rust ã®ãªãã©ã«ãä¿æããŸãããã®æ®µéã§ã¯ nested.first() ãã Some(each="foo") ã®ãããªåœ¢åŒãè¿ã£ãŠããããšãæåŸ
ããŠããã®ã§ Lit ã§ã¯ãªã Meta ã«ãããããããã«ããŸãã Meta(Meta) A structured meta item, like the Copy in #[derive(Copy)] which would be a nested Meta::Path. Lit(Lit) A Rust literal, like the “new_name” in #[rename(“new_name”)]. let ident_each_name = field .attrs .first() .map(|attr| match attr.parse_meta() { Ok(Meta::List(list)) => match list.nested.first() { Some(NestedMeta::Meta(_)) => todo!(), _ => None, }, _ => None, }); å
ã»ã©ã¯ãã¿ãŒã³ããããçšã㊠Meta::List ãååŸããŸããããäžè¿°ã®ããã«ä»åºŠã¯ each="foo" ã®ãããªããŒãšããªã¥ãŒã®ãã¢ãååŸãããããšãæåŸ
ããŠããã®ã§ã Meta::NameValue(MetaNameValue) ããããããŠåŠçããŸãã let ident_each_name = field .attrs .first() .map(|attr| match attr.parse_meta() { Ok(Meta::List(list)) => match list.nested.first() { Some(NestedMeta::Meta(Meta::NameValue(MetaNameValue{}))) => todo!(), _ => None, }, _ => None, }); MetaNameValue ã¯ä»¥äžã®ãããªæ§é äœã§ãã pub struct MetaNameValue { pub path: Path, pub eq_token: Eq, pub lit: Lit, } each = "foo" ãäŸã«ãšããšã MetaNameValue 㯠path ã« each ãã lit ã« "foo" ãæ ŒçŽããŸããä»å欲ããã®ã¯ "foo" ã®æ¹ãªã®ã§ lit ã ããååŸããã°è¯ãããã§ãã let ident_each_name = field .attrs .first() .map(|attr| match attr.parse_meta() { Ok(Meta::List(list)) => match list.nested.first() { Some(NestedMeta::Meta(Meta::NameValue(MetaNameValue{ path: _, eq_token: _, lit }))) => todo!(), _ => None, }, _ => None, }); Lit ã¯ä»¥äžã®ãã㪠enum ã§ãã each = "foo" ã®ãããªåœ¢åŒããããããããã«ãæååãªãã©ã«ïŒ Lit::Str ïŒãåŸãããããšãæåŸ
ããŠããŸãã pub enum Lit { Str(LitStr), ByteStr(LitByteStr), Byte(LitByte), Char(LitChar), Int(LitInt), Float(LitFloat), Bool(LitBool), Verbatim(Literal), } ãªãã©ã«ã衚ãå€ã¯ value ã¡ãœããã§ååŸã§ããã®ã§ã let ident_each_name = field .attrs .first() .map(|attr| match attr.parse_meta() { Ok(Meta::List(list)) => match list.nested.first() { Some(NestedMeta::Meta(Meta::NameValue(MetaNameValue{ path: _, eq_token: _, lit: Lit::Str(ref s) }))) => { Some(s.value()) }, _ => None, }, _ => None, }) .flatten(); 以äžã§åãã£ãŒã«ãã®èŠçŽ è¿œå çšã®ã¡ãœããåãååŸã§ããŸããã Vec åã®ãã£ãŒã«ãã® setter ãäžæ¬æŽæ°çšãšèŠçŽ è¿œå çšã® 2 çš®é¡çæãã ä»ãŸã§ã¯ãã£ãŒã«ãã®åã«ããããåçŽã«ä»¥äžã®ããã« setter ãçæããŠããŸããã quote! { pub fn #ident(&mut self, #ident: #t) -> &mut Self { self.#ident = Some(#ident); self } } ãŸãã¢ããªãã¥ãŒããä»äžãããŠãããã©ããã§åå²ãçºçããŸããã¢ããªãã¥ãŒããä»äžãããŠãããšèŠçŽ è¿œå çšã®ã¡ãœãããå¿
èŠã«ãªããŸãã match ident_each_name { Some(name) => todo!(), None => todo!(), } ãŸãã¯èŠçŽ è¿œå çšã®ã¡ãœããããããªãæ¹ãå®è£
ããŸãã Vec åã®ãã£ãŒã«ã㯠Option ã§ã©ãããããªãã®ã§ Vec åãã©ããã§ setter ã®å®è£
ãå€ãããŸãã match ident_each_name { Some(name) => todo!(), None => { if is_vector(&ty) { quote! { pub fn #ident(&mut self, #ident: #ty) -> &mut Self { self.#ident = #ident; self } } } else { quote! { pub fn #ident(&mut self, #ident: #ty) -> &mut Self { self.#ident = Some(#ident); self } } } }, } 次ã«èŠçŽ è¿œå çšã®ã¡ãœãããå¿
èŠãªãã¿ãŒã³ãå®è£
ããŸãããã£ã¡ã¯ä»¥äžã® 2 ã€ã®ãã¿ãŒã³ã§åŠçãåå²ããŸãã èŠçŽ è¿œå çšã®ã¡ãœããåããã£ãŒã«ãåãš åã èŠçŽ è¿œå çšã®ã¡ãœããã®ã¿çæãã èŠçŽ è¿œå çšã®ã¡ãœããåããã£ãŒã«ãåãš ç°ãªã èŠçŽ è¿œå çšã®ã¡ãœãããšäžæ¬æŽæ°çšã®ã¡ãœãããäž¡æ¹çæãã å®è£
ã¯ä»¥äžã®ããã«ãªããŸããå®è£
ã®ãã€ã³ãã¯ä»¥äžã®éãã§ãã èŠçŽ è¿œå çšã®é¢æ°ã®åŒæ°ã®åãšããŠäœ¿çšããããã« unwrap_vector ã§äžèº«ã®åãåãåºã èŠçŽ è¿œå çšã®é¢æ°ã®ååïŒ name ïŒã¯ String ãªã®ã§ Ident::new ã§ Ident ãçæãã Ident::new ã®ç¬¬äºåŒæ°ã«ã¯ Span æ§é äœãæå®ããå¿
èŠãããã Span æ§é äœã¯ãã¯ãã®å±éå
ã§èå¥åã誀ã£ãŠææãããªãããã«ããããã«å¿
èŠïŒ åè ïŒ match ident_each_name { Some(name) => { let ty_each = unwrap_vector(ty).unwrap(); let ident_each = Ident::new(name.as_str(), Span::call_site()); if ident.unwrap().to_string() == name { quote! { pub fn #ident_each(&mut self, #ident_each:#ty_each) -> &mut Self { self.#ident.push(#ident_each); self } } } else { quote! { pub fn #ident(&mut self, #ident: #ty) -> &mut Self { self.#ident = #ident; self } pub fn #ident_each(&mut self, #ident_each: #ty_each) -> &mut Self { self.#ident.push(#ident_each); self } } } } None => { (ç¥) }, } æçµçãªå®è£
ã¯ä»¥äžã®ããã«ãªããŸãã use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote}; use syn::{ parse_macro_input, Data, DeriveInput, Fields, FieldsNamed, GenericArgument, Ident, Lit, Meta, MetaList, MetaNameValue, NestedMeta, PathArguments, PathSegment, Type, Visibility, }; #[proc_macro_derive(Builder, attributes(builder))] pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); let ident = input.ident; let vis = input.vis; let builder_name = format_ident!("{}Builder", ident); let fields = match input.data { Data::Struct(data) => match data.fields { Fields::Named(fields) => fields, _ => panic!("no unnamed fields are allowed"), }, _ => panic!("this macro can be applied only to struct"), }; let builder_struct = build_builder_struct(&fields, &builder_name, &vis); let builder_impl = build_builder_impl(&fields, &builder_name, &ident); let struct_impl = build_struct_impl(&fields, &builder_name, &ident); let expand = quote! { #builder_struct #builder_impl #struct_impl }; proc_macro::TokenStream::from(expand) } fn build_builder_struct( fields: &FieldsNamed, builder_name: &Ident, visibility: &Visibility, ) -> TokenStream { let struct_fields = fields .named .iter() .map(|field| { let ident = field.ident.as_ref(); let ty = unwrap_option(&field.ty).unwrap_or(&field.ty); (ident.unwrap(), ty) }) .map(|(ident, ty)| { if is_vector(&ty) { quote! { #ident: #ty } } else { quote! { #ident: Option<#ty> } } }); quote! { #visibility struct #builder_name { #(#struct_fields),* } } } fn build_builder_impl( fields: &FieldsNamed, builder_name: &Ident, struct_name: &Ident, ) -> TokenStream { let checks = fields .named .iter() .filter(|field| !is_option(&field.ty)) .filter(|field| !is_vector(&field.ty)) .map(|field| { let ident = field.ident.as_ref(); let err = format!("Required field '{}' is missing", ident.unwrap().to_string()); quote! { if self.#ident.is_none() { return Err(#err.into()); } } }); let setters = fields.named.iter().map(|field| { let ident_each_name = field .attrs .first() .map(|attr| match attr.parse_meta() { Ok(Meta::List(list)) => match list.nested.first() { Some(NestedMeta::Meta(Meta::NameValue(MetaNameValue { path: _, eq_token: _, lit: Lit::Str(ref str), }))) => Some(str.value()), _ => None, }, _ => None, }) .flatten(); let ident = field.ident.as_ref(); let ty = unwrap_option(&field.ty).unwrap_or(&field.ty); match ident_each_name { Some(name) => { let ty_each = unwrap_vector(ty).unwrap(); let ident_each = Ident::new(name.as_str(), Span::call_site()); if ident.unwrap().to_string() == name { quote! { pub fn #ident_each(&mut self, #ident_each:#ty_each) -> &mut Self { self.#ident.push(#ident_each); self } } } else { quote! { pub fn #ident(&mut self, #ident: #ty) -> &mut Self { self.#ident = #ident; self } pub fn #ident_each(&mut self, #ident_each: #ty_each) -> &mut Self { self.#ident.push(#ident_each); self } } } } None => { if is_vector(&ty) { quote! { pub fn #ident(&mut self, #ident: #ty) -> &mut Self { self.#ident = #ident; self } } } else { quote! { pub fn #ident(&mut self, #ident: #ty) -> &mut Self { self.#ident = Some(#ident); self } } } } } }); let struct_fields = fields.named.iter().map(|field| { let ident = field.ident.as_ref(); if is_option(&field.ty) || is_vector(&field.ty) { quote! { #ident: self.#ident.clone() } } else { quote! { #ident: self.#ident.clone().unwrap() } } }); quote! { impl #builder_name { #(#setters)* pub fn build(&mut self) -> Result<#struct_name, Box<dyn std::error::Error>> { #(#checks)* Ok(#struct_name { #(#struct_fields),* }) } } } } fn build_struct_impl( fields: &FieldsNamed, builder_name: &Ident, struct_name: &Ident, ) -> TokenStream { let field_defaults = fields.named.iter().map(|field| { let ident = field.ident.as_ref(); let ty = &field.ty; if is_vector(&ty) { quote! { #ident: Vec::new() } } else { quote! { #ident: None } } }); quote! { impl #struct_name { pub fn builder() -> #builder_name { #builder_name { #(#field_defaults),* } } } } } fn is_option(ty: &Type) -> bool { match get_last_path_segment(ty) { Some(seg) => seg.ident == "Option", _ => false, } } fn is_vector(ty: &Type) -> bool { match get_last_path_segment(ty) { Some(seg) => seg.ident == "Vec", _ => false, } } fn unwrap_option(ty: &Type) -> Option<&Type> { if !is_option(ty) { return None; } unwrap_generic_type(ty) } fn unwrap_vector(ty: &Type) -> Option<&Type> { if !is_vector(ty) { return None; } unwrap_generic_type(ty) } fn unwrap_generic_type(ty: &Type) -> Option<&Type> { match get_last_path_segment(ty) { Some(seg) => match seg.arguments { PathArguments::AngleBracketed(ref args) => { args.args.first().and_then(|arg| match arg { &GenericArgument::Type(ref ty) => Some(ty), _ => None, }) } _ => None, }, None => None, } } fn get_last_path_segment(ty: &Type) -> Option<&PathSegment> { match ty { Type::Path(path) => path.path.segments.last(), _ => None, } } 08-unrecognized-attribute ç®æš attribute ã«ééã£ãèå¥åãäžããããéã«é©åãªã³ã³ãã€ã«ãšã©ãŒã衚瀺ãã å®è£
æ¹é ã¢ããªãã¥ãŒãã®ããŒãšã㊠each 以å€ã®ãã®ãäžããããå Žåã«ãšã©ãŒã衚瀺ãã ãšã©ãŒãçºçããããç®æã§ syn::Error ã® to_compile_error ã¡ãœããã§ TokenStream ãè¿ãããã«ãã åçŽã« panic! ãããã ãããã詳现ãªãšã©ãŒã¡ãã»ãŒãžã衚瀺ããããã å®è£
ã¢ããªãã¥ãŒãã®ããŒãæ£ãããå€å®ããå¿
èŠãããã®ã§ãã¢ããªãã¥ãŒããåŠçããŠãã以äžã®ç®æã倿ŽããŸãã let ident_each_name = field .attrs .first() .map(|attr| match attr.parse_meta() { Ok(Meta::List(list)) => match list.nested.first() { Some(NestedMeta::Meta(Meta::NameValue(MetaNameValue { path: _, eq_token: _, lit: Lit::Str(ref str), }))) => Some(str.value()), _ => None, }, _ => None, }) .flatten(); ã¢ããªãã¥ãŒãã®ããŒã¯ MetadataNameValue ã® path ã«æ ŒçŽãããŠããŸãã path 㯠Path åãªã®ã§ 06-optional-field ã§åŠçããã®ãšåæ§ã«ããŠèå¥åãååŸããŸãã Ident ã® to_string ã¡ãœããã§èå¥åã®ååãååŸã§ããã®ã§ãããã each ãšäžèŽããªããã°ãšã©ãŒãè¿ãã°è¯ãããã§ãã let ident_each_name = field .attrs .first() .map(|attr| match attr.parse_meta() { Ok(Meta::List(list)) => match list.nested.first() { Some(NestedMeta::Meta(Meta::NameValue(MetaNameValue { ref path, eq_token: _, lit: Lit::Str(ref str), }))) => { if let Some(name) = path.segments.first() { if name.ident.to_string() != "each" { todo!() } } Some(str.value()) } _ => None, }, _ => None, }) .flatten(); syn::Error 㯠syn::Error::new_spanned() ã䜿ã£ãŠçæããŸãããšã©ãŒã¡ãã»ãŒãžïŒ”expected …”ïŒã¯ãã¹ãã±ãŒã¹ã«èšèŒãããã¡ãã»ãŒãžããã®ãŸãŸäœ¿ããŸãã以äžã®ããã«ããããšããã§ããããã®ãŸãŸã§ã¯åãåããªãã®ã§ã³ã³ãã€ã«ã§ããŸããã let ident_each_name = field .attrs .first() .map(|attr| match attr.parse_meta() { Ok(Meta::List(list)) => match list.nested.first() { Some(NestedMeta::Meta(Meta::NameValue(MetaNameValue { ref path, eq_token: _, lit: Lit::Str(ref str), }))) => { if let Some(name) = path.segments.first() { if name.ident.to_string() != "each" { return Some(syn::Error::new_spanned( list, "expected `builder(each = \"...\")`", )); } } Some(str.value()) } _ => None, }, _ => None, }) .flatten(); ããã§ã以äžã®ãã㪠enum ãè¿ãããã«ããŠãåŸã§ãã¿ãŒã³ãããã§åŠçããŸãããã enum LitOrError { Lit(String), Error(syn::Error), } LitOrError ã䜿ã£ãŠå
ã»ã©ã®ç®æã次ã®ããã«æžãæããŸãã let ident_each_name = field .attrs .first() .map(|attr| match attr.parse_meta() { Ok(Meta::List(list)) => match list.nested.first() { Some(NestedMeta::Meta(Meta::NameValue(MetaNameValue { ref path, eq_token: _, lit: Lit::Str(ref str), }))) => { if let Some(name) = path.segments.first() { if name.ident.to_string() != "each" { return Some(LitOrError::Error(syn::Error::new_spanned( list, "expected `builder(each = \"...\")`", ))); } } Some(LitOrError::Lit(str.value())) } _ => None, }, _ => None, }) .flatten(); ãã¿ãŒã³ãããã§åŠçããŠããéšåã enum ã«åãããŠå€æŽããŸãã LitOrError::Error ã«ãããããå Žåã¯ã³ã³ãã€ã«ãšã©ãŒãçããããå¿
èŠãããã®ã§ã to_compile_error().into() ã§ãšã©ãŒãè¿ããŸãã match ident_each_name { Some(LitOrError::Lit(name)) => { (ç¥) } Some(LitOrError::Error(err)) => err.to_compile_error().into(), None => { (ç¥) } } ä»ãŸã§ panic! ããŠããå Žæã syn::Error ã® to_compile_error ã¡ãœããã䜿ã£ãŠ TokenStream ãè¿ãããã«å€æŽããŠãããŸãã let fields = match input.data { Data::Struct(data) => match data.fields { Fields::Named(fields) => fields, _ => { return syn::Error::new(ident.span(), "expects named fields") .to_compile_error() .into() } }, _ => { return syn::Error::new(ident.span(), "expects struct") .to_compile_error() .into() } }; æçµçãªå®è£
ã¯ä»¥äžã®ããã«ãªããŸãã use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote}; use syn::{ parse_macro_input, Data, DeriveInput, Fields, FieldsNamed, GenericArgument, Ident, Lit, Meta, MetaNameValue, NestedMeta, PathArguments, PathSegment, Type, Visibility, }; enum LitOrError { Lit(String), Error(syn::Error), } #[proc_macro_derive(Builder, attributes(builder))] pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); let ident = input.ident; let vis = input.vis; let builder_name = format_ident!("{}Builder", ident); let fields = match input.data { Data::Struct(data) => match data.fields { Fields::Named(fields) => fields, _ => { return syn::Error::new(ident.span(), "expects named fields") .to_compile_error() .into() } }, _ => { return syn::Error::new(ident.span(), "expects struct") .to_compile_error() .into() } }; let builder_struct = build_builder_struct(&fields, &builder_name, &vis); let builder_impl = build_builder_impl(&fields, &builder_name, &ident); let struct_impl = build_struct_impl(&fields, &builder_name, &ident); let expand = quote! { #builder_struct #builder_impl #struct_impl }; proc_macro::TokenStream::from(expand) } fn build_builder_struct( fields: &FieldsNamed, builder_name: &Ident, visibility: &Visibility, ) -> TokenStream { let struct_fields = fields .named .iter() .map(|field| { let ident = field.ident.as_ref(); let ty = unwrap_option(&field.ty).unwrap_or(&field.ty); (ident.unwrap(), ty) }) .map(|(ident, ty)| { if is_vector(&ty) { quote! { #ident: #ty } } else { quote! { #ident: Option<#ty> } } }); quote! { #visibility struct #builder_name { #(#struct_fields),* } } } fn build_builder_impl( fields: &FieldsNamed, builder_name: &Ident, struct_name: &Ident, ) -> TokenStream { let checks = fields .named .iter() .filter(|field| !is_option(&field.ty)) .filter(|field| !is_vector(&field.ty)) .map(|field| { let ident = field.ident.as_ref(); let err = format!("Required field '{}' is missing", ident.unwrap().to_string()); quote! { if self.#ident.is_none() { return Err(#err.into()); } } }); let setters = fields.named.iter().map(|field| { let ident_each_name = field .attrs .first() .map(|attr| match attr.parse_meta() { Ok(Meta::List(list)) => match list.nested.first() { Some(NestedMeta::Meta(Meta::NameValue(MetaNameValue { ref path, eq_token: _, lit: Lit::Str(ref str), }))) => { if let Some(name) = path.segments.first() { if name.ident.to_string() != "each" { return Some(LitOrError::Error(syn::Error::new_spanned( list, "expected `builder(each = \"...\")`", ))); } } Some(LitOrError::Lit(str.value())) } _ => None, }, _ => None, }) .flatten(); let ident = field.ident.as_ref(); let ty = unwrap_option(&field.ty).unwrap_or(&field.ty); match ident_each_name { Some(LitOrError::Lit(name)) => { let ty_each = unwrap_vector(ty).unwrap(); let ident_each = Ident::new(name.as_str(), Span::call_site()); if ident.unwrap().to_string() == name { quote! { pub fn #ident_each(&mut self, #ident_each:#ty_each) -> &mut Self { self.#ident.push(#ident_each); self } } } else { quote! { pub fn #ident(&mut self, #ident: #ty) -> &mut Self { self.#ident = #ident; self } pub fn #ident_each(&mut self, #ident_each: #ty_each) -> &mut Self { self.#ident.push(#ident_each); self } } } } Some(LitOrError::Error(err)) => err.to_compile_error().into(), None => { if is_vector(&ty) { quote! { pub fn #ident(&mut self, #ident: #ty) -> &mut Self { self.#ident = #ident; self } } } else { quote! { pub fn #ident(&mut self, #ident: #ty) -> &mut Self { self.#ident = Some(#ident); self } } } } } }); let struct_fields = fields.named.iter().map(|field| { let ident = field.ident.as_ref(); if is_option(&field.ty) || is_vector(&field.ty) { quote! { #ident: self.#ident.clone() } } else { quote! { #ident: self.#ident.clone().unwrap() } } }); quote! { impl #builder_name { #(#setters)* pub fn build(&mut self) -> Result<#struct_name, Box<dyn std::error::Error>> { #(#checks)* Ok(#struct_name { #(#struct_fields),* }) } } } } fn build_struct_impl( fields: &FieldsNamed, builder_name: &Ident, struct_name: &Ident, ) -> TokenStream { let field_defaults = fields.named.iter().map(|field| { let ident = field.ident.as_ref(); let ty = &field.ty; if is_vector(&ty) { quote! { #ident: Vec::new() } } else { quote! { #ident: None } } }); quote! { impl #struct_name { pub fn builder() -> #builder_name { #builder_name { #(#field_defaults),* } } } } } fn is_option(ty: &Type) -> bool { match get_last_path_segment(ty) { Some(seg) => seg.ident == "Option", _ => false, } } fn is_vector(ty: &Type) -> bool { match get_last_path_segment(ty) { Some(seg) => seg.ident == "Vec", _ => false, } } fn unwrap_option(ty: &Type) -> Option<&Type> { if !is_option(ty) { return None; } unwrap_generic_type(ty) } fn unwrap_vector(ty: &Type) -> Option<&Type> { if !is_vector(ty) { return None; } unwrap_generic_type(ty) } fn unwrap_generic_type(ty: &Type) -> Option<&Type> { match get_last_path_segment(ty) { Some(seg) => match seg.arguments { PathArguments::AngleBracketed(ref args) => { args.args.first().and_then(|arg| match arg { &GenericArgument::Type(ref ty) => Some(ty), _ => None, }) } _ => None, }, None => None, } } fn get_last_path_segment(ty: &Type) -> Option<&PathSegment> { match ty { Type::Path(path) => path.path.segments.last(), _ => None, } } 09-redefined-prelude-types ç®æš std::prelude ã§ã€ã³ããŒããããåïŒ Option ã Vector ïŒãªã©ããŠãŒã¶ãŒã«ãã£ãŠåå®çŸ©ãããŠãæ£ãã䜿ããããã«ãã å®è£
æ¹é Option ã Vec ãªã©ãåå空éãæå®ããŠäœ¿ãããã«ããã°ããã§ãããã¹ãã³ãŒãã§ãã§ãã¯ãããŠããã®ã¯ä»¥äžã® 5 ã€ãªã®ã§ãä»åã¯ãããã®é©åãªåå空éãæå®ããããã«å€æŽããŸãã 倿Žå 倿ŽåŸ Option std::option::Option Some std::option::Option::Some None std::option::Option::None Result std::result::Result Box std::boxed::Box åå®çŸ©ã®åœ±é¿ãåããã®ã¯ãã¯ããåŒã³åºãããŠå±éãããéšåã®ã¿ãªã®ã§ãçŽãã®ã¯ quote ãã¯ãã®äžã«ããéšåã ãã§ååã§ãã å®è£
äžè¿°ã® 5 ã€ã®åå空éãæ£ããæå®ããã ããªã®ã§ãä»åã¯æçµçãªçµæã®ã¿ãèšèŒããŸããæçµçãªå®è£
ã¯ä»¥äžã®ããã«ãªããŸãã use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote}; use syn::{ parse_macro_input, Data, DeriveInput, Fields, FieldsNamed, GenericArgument, Ident, Lit, Meta, MetaNameValue, NestedMeta, PathArguments, PathSegment, Type, Visibility, }; enum LitOrError { Lit(String), Error(syn::Error), } #[proc_macro_derive(Builder, attributes(builder))] pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); let ident = input.ident; let vis = input.vis; let builder_name = format_ident!("{}Builder", ident); let fields = match input.data { Data::Struct(data) => match data.fields { Fields::Named(fields) => fields, _ => { return syn::Error::new(ident.span(), "expects named fields") .to_compile_error() .into() } }, _ => { return syn::Error::new(ident.span(), "expects struct") .to_compile_error() .into() } }; let builder_struct = build_builder_struct(&fields, &builder_name, &vis); let builder_impl = build_builder_impl(&fields, &builder_name, &ident); let struct_impl = build_struct_impl(&fields, &builder_name, &ident); let expand = quote! { #builder_struct #builder_impl #struct_impl }; proc_macro::TokenStream::from(expand) } fn build_builder_struct( fields: &FieldsNamed, builder_name: &Ident, visibility: &Visibility, ) -> TokenStream { let struct_fields = fields .named .iter() .map(|field| { let ident = field.ident.as_ref(); let ty = unwrap_option(&field.ty).unwrap_or(&field.ty); (ident.unwrap(), ty) }) .map(|(ident, ty)| { if is_vector(&ty) { quote! { #ident: #ty } } else { quote! { #ident: std::option::Option<#ty> } } }); quote! { #visibility struct #builder_name { #(#struct_fields),* } } } fn build_builder_impl( fields: &FieldsNamed, builder_name: &Ident, struct_name: &Ident, ) -> TokenStream { let checks = fields .named .iter() .filter(|field| !is_option(&field.ty)) .filter(|field| !is_vector(&field.ty)) .map(|field| { let ident = field.ident.as_ref(); let err = format!("Required field '{}' is missing", ident.unwrap().to_string()); quote! { if self.#ident.is_none() { return Err(#err.into()); } } }); let setters = fields.named.iter().map(|field| { let ident_each_name = field .attrs .first() .map(|attr| match attr.parse_meta() { Ok(Meta::List(list)) => match list.nested.first() { Some(NestedMeta::Meta(Meta::NameValue(MetaNameValue { ref path, eq_token: _, lit: Lit::Str(ref str), }))) => { if let Some(name) = path.segments.first() { if name.ident.to_string() != "each" { return Some(LitOrError::Error(syn::Error::new_spanned( list, "expected `builder(each = \"...\")`", ))); } } Some(LitOrError::Lit(str.value())) } _ => None, }, _ => None, }) .flatten(); let ident = field.ident.as_ref(); let ty = unwrap_option(&field.ty).unwrap_or(&field.ty); match ident_each_name { Some(LitOrError::Lit(name)) => { let ty_each = unwrap_vector(ty).unwrap(); let ident_each = Ident::new(name.as_str(), Span::call_site()); if ident.unwrap().to_string() == name { quote! { pub fn #ident_each(&mut self, #ident_each:#ty_each) -> &mut Self { self.#ident.push(#ident_each); self } } } else { quote! { pub fn #ident(&mut self, #ident: #ty) -> &mut Self { self.#ident = #ident; self } pub fn #ident_each(&mut self, #ident_each: #ty_each) -> &mut Self { self.#ident.push(#ident_each); self } } } } Some(LitOrError::Error(err)) => err.to_compile_error().into(), None => { if is_vector(&ty) { quote! { pub fn #ident(&mut self, #ident: #ty) -> &mut Self { self.#ident = #ident; self } } } else { quote! { pub fn #ident(&mut self, #ident: #ty) -> &mut Self { self.#ident = std::option::Option::Some(#ident); self } } } } } }); let struct_fields = fields.named.iter().map(|field| { let ident = field.ident.as_ref(); if is_option(&field.ty) || is_vector(&field.ty) { quote! { #ident: self.#ident.clone() } } else { quote! { #ident: self.#ident.clone().unwrap() } } }); quote! { impl #builder_name { #(#setters)* pub fn build(&mut self) -> std::result::Result<#struct_name, std::boxed::Box<dyn std::error::Error>> { #(#checks)* Ok(#struct_name { #(#struct_fields),* }) } } } } fn build_struct_impl( fields: &FieldsNamed, builder_name: &Ident, struct_name: &Ident, ) -> TokenStream { let field_defaults = fields.named.iter().map(|field| { let ident = field.ident.as_ref(); let ty = &field.ty; if is_vector(&ty) { quote! { #ident: std::vec::Vec::new() } } else { quote! { #ident: std::option::Option::None } } }); quote! { impl #struct_name { pub fn builder() -> #builder_name { #builder_name { #(#field_defaults),* } } } } } fn is_option(ty: &Type) -> bool { match get_last_path_segment(ty) { Some(seg) => seg.ident == "Option", _ => false, } } fn is_vector(ty: &Type) -> bool { match get_last_path_segment(ty) { Some(seg) => seg.ident == "Vec", _ => false, } } fn unwrap_option(ty: &Type) -> Option<&Type> { if !is_option(ty) { return None; } unwrap_generic_type(ty) } fn unwrap_vector(ty: &Type) -> Option<&Type> { if !is_vector(ty) { return None; } unwrap_generic_type(ty) } fn unwrap_generic_type(ty: &Type) -> Option<&Type> { match get_last_path_segment(ty) { Some(seg) => match seg.arguments { PathArguments::AngleBracketed(ref args) => { args.args.first().and_then(|arg| match arg { &GenericArgument::Type(ref ty) => Some(ty), _ => None, }) } _ => None, }, None => None, } } fn get_last_path_segment(ty: &Type) -> Option<&PathSegment> { match ty { Type::Path(path) => path.path.segments.last(), _ => None, } } ãŸãšã builder ãã¯ãã顿ã«ããŠåç·šãšåŸç·šã«åããŠæç¶ãçãã¯ãã®å®è£
æ¹æ³ã説æããŠããŸããã ä»åå®è£
ãããã¯ãã¯ãã£ãŒã«ãã®åã Option<Vec<_>> ã§ããã±ãŒã¹ãã Vec åã®ãã£ãŒã«ã以å€ã« each ãä»äžããå Žåãªã©ãèæ
®ããŠããããå®è£
ããæ©èœã¯ååã§ã¯ãããŸããããã¹ããååãªã±ãŒã¹ãç¶²çŸ
ããŠãããšã¯èšããŸããã ãããä»åã®èšäºã§æç¶ãçãã¯ããã©ã®ããã«ããŠäœããã¯äžéãçè§£ã§ããä»åŸããæãã©ãããæãã€ããã°ãããã ãããæèŠãã€ãããããšæããŸãããã®èšäºãä»åŸã¿ãªããããã¯ããå®è£
ãããšãã®å©ãã«ãªãã°å¹žãã§ãã åèæç® syn – Docs.rs The post proc_macro_workshopã§Rustã®æç¶ãçãã¯ãã«å
¥éãã åŸç·š appeared first on CADDi Tech Blog .