proc-macro-workshop:builder-4
审题
// Generate a `build` method to go from builder to original struct.
//
// This method should require that every one of the fields has been explicitly
// set; it should return an error if a field is missing. The precise error type
// is not important. Consider using Box<dyn Error>, which you can construct
// using the impl From<String> for Box<dyn Error>.
//
// impl CommandBuilder {
// pub fn build(&mut self) -> Result<Command, Box<dyn Error>> {
// ...
// }
// }
use derive_builder::Builder;
#[derive(Builder)]
pub struct Command {
executable: String,
args: Vec<String>,
env: Vec<String>,
current_dir: String,
}
fn main() {
let mut builder = Command::builder();
builder.executable("cargo".to_owned());
builder.args(vec!["build".to_owned(), "--release".to_owned()]);
builder.env(vec![]);
builder.current_dir("..".to_owned());
let command = builder.build().unwrap();
assert_eq!(command.executable, "cargo");
}
如提示所言,我们这一关主要的是实现build
方法,同时注意返回结果类型为Result
。
原来的数据我们设置在了XBuilder
中,这一关就需要从XBuilder
中反向注入参数了。
这里需要扩展的是,一般操作过程错误处理需要返回
Result
。
并且对于一个Command
而言,可以有一些缺省参数。
在这里,我们将检查全部字段,如果没有设置,返回异常。
虽然这里没有对异常进行检查,但是后续的问题中,对异常的检查会十分严格。
题解
pub(super) fn solution(
fields: &crate::common::FieldsType,
builder_ident: &syn::Ident,
origin_ident: &syn::Ident,
) -> proc_macro2::TokenStream {
let idents: Vec<_> = fields.iter().map(|f| &f.ident).collect();
quote::quote! {
impl #builder_ident {
pub fn build(&self) -> std::result::Result<#origin_ident, std::boxed::Box<dyn std::error::Error>> {
// 异常检查展开
#(
if self.#idents.is_none() {
let err = std::format!("field {} missing", std::stringify!(#idents));
return std::result::Result::Err(err.into());
}
)*
// 数值回传
let res = #origin_ident {
#(
#idents: self.#idents.clone().unwrap()
),*
};
std::result::Result::Ok(res)
}
}
}
}
整体
fn solution1(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = syn::parse_macro_input!(input as syn::DeriveInput);
let fields = {
match common::parse_fields(&ast) {
Ok(f) => f,
Err(_e) => std::panic!(std::stringify!(_e)),
}
};
let origin_ident = &ast.ident;
let builder_ident = "e::format_ident!("{}Builder", origin_ident);
let mut token_stream = proc_macro2::TokenStream::new();
// solution2
let solution2_stream = solution2::solution(fields, origin_ident, builder_ident);
token_stream.extend(solution2_stream);
// solution35
let solution3_stream = solution35::soultion(fields, builder_ident);
token_stream.extend(solution3_stream);
// solution4
let solution45_stream = solution4::solution(fields, builder_ident, origin_ident);
token_stream.extend(solution45_stream);
proc_macro::TokenStream::from(token_stream)
}
本作品采用《CC 协议》,转载必须注明作者和本文链接