2015-10-16 58 views
2

是否可以编写一个生成函数的宏,该函数的参数数目由宏决定?例如,我想写一些东西,使Cassandra驱动程序中准备好的语句更容易。通过宏确定的参数生成函数的宏

let prepared = prepare!(session, "insert into blah (id, name, reading) values (?, ?, ?)", int, string, float); 
let stmt = prepared(1, "test".to_string(), 3.1); 
session.execute(stmt); 

prepare!需要生成像(只打开这里为简便起见):拉斯特宏

fn some_func(arg1, arg2, arg3) -> Statement { 
    let mut statement = Statement::new("insert into blah (id, name, reading) values (?, ?, ?)", 3); 
    statement.bind_int(0, arg1).unwrap() 
     .bind_string(1, arg2).unwrap() 
     .bind_float(2, arg3).unwrap() 
} 
+0

请删除您的第二个问题,关于“这是可能的”,并且[问一个单独的问题] (http://meta.stackexchange.com/questions/39223/one-post-with-multiple-questions-or-multiple-posts)。 – Shepmaster

回答

7

两个坚硬的东西:计数和独特identifers。你有两个。然后再次,我是写答案的人,所以我想现在是我的问题。至少你没有问过解析字符串(如果没有编译器插件,这是完全不可能的)。

另一个不可能的事情是将类型映射到不同的方法。你不能。相反,我会假设存在一个帮助特征来完成这种映射。

而且,锈没有intstringfloat。我假设你的意思是i32Stringf32

最后,您编写调用和扩展的方式并不真正凝聚。我不明白为什么session涉及;它没有用于扩展。所以我会冒昧地假装你不需要它;如果你这样做,你将不得不把它砍回来。

所以,这就是我想出来的。

// Some dummy types so the following will type-check. 

struct Statement; 

impl Statement { 
    fn new(stmt: &str, args: usize) -> Self { Statement } 
    fn bind_int(self, pos: usize, value: i32) -> Result<Self,()> { Ok(self) } 
    fn bind_float(self, pos: usize, value: f32) -> Result<Self,()> { Ok(self) } 
    fn bind_string(self, pos: usize, value: String) -> Result<Self,()> { Ok(self) } 
} 

struct Session; 

impl Session { 
    fn execute(&self, stmt: Statement) {} 
} 

// The supporting `BindArgument` trait. 

trait BindArgument { 
    fn bind(stmt: Statement, pos: usize, value: Self) -> Statement; 
} 

impl BindArgument for i32 { 
    fn bind(stmt: Statement, pos: usize, value: Self) -> Statement { 
     stmt.bind_int(pos, value).unwrap() 
    } 
} 

impl BindArgument for f32 { 
    fn bind(stmt: Statement, pos: usize, value: Self) -> Statement { 
     stmt.bind_float(pos, value).unwrap() 
    } 
} 

impl BindArgument for String { 
    fn bind(stmt: Statement, pos: usize, value: Self) -> Statement { 
     stmt.bind_string(pos, value).unwrap() 
    } 
} 

// The macro itself. 

macro_rules! prepare { 
    // These three are taken straight from 
    // https://danielkeep.github.io/tlborm/book/ 
    (@as_expr $e:expr) => {$e}; 

    (@count_tts $($tts:tt)*) => { 
     <[()]>::len(&[$(prepare!(@replace_tt $tts())),*]) 
    }; 

    (@replace_tt $_tt:tt $e:expr) => {$e}; 

    // This is how we bind *one* argument. 

    (@bind_arg $stmt:expr, $args:expr, $pos:tt, $t:ty) => { 
     prepare!(@as_expr <$t as BindArgument>::bind($stmt, $pos, $args.$pos)) 
    }; 

    // This is how we bind *N* arguments. Note that because you can't do 
    // arithmetic in macros, we have to spell out every supported integer. 
    // This could *maybe* be factored down with some more work, but that 
    // can be homework. ;) 

    (@bind_args $stmt:expr, $args:expr, 0, $next:ty, $($tys:ty,)*) => { 
     prepare!(@bind_args prepare!(@bind_arg $stmt, $args, 0, $next), $args, 1, $($tys,)*) 
    }; 

    (@bind_args $stmt:expr, $args:expr, 1, $next:ty, $($tys:ty,)*) => { 
     prepare!(@bind_args prepare!(@bind_arg $stmt, $args, 1, $next), $args, 2, $($tys,)*) 
    }; 

    (@bind_args $stmt:expr, $args:expr, 2, $next:ty, $($tys:ty,)*) => { 
     prepare!(@bind_args prepare!(@bind_arg $stmt, $args, 2, $next), $args, 3, $($tys,)*) 
    }; 

    (@bind_args $stmt:expr, $_args:expr, $_pos:tt,) => { 
     $stmt 
    }; 

    // Finally, the entry point of the macro. 

    ($stmt:expr, $($tys:ty),* $(,)*) => { 
     { 
      // I cheated: rather than face the horror of trying to *also* do 
      // unique identifiers, I just shoved the arguments into a tuple, so 
      // that I could just re-use the position. 
      fn prepared_statement(args: ($($tys,)*)) -> Statement { 
       let statement = Statement::new(
        $stmt, 
        prepare!(@count_tts $(($tys))*)); 
       prepare!(@bind_args statement, args, 0, $($tys,)*) 
      } 
      prepared_statement 
     } 
    }; 
} 

fn main() { 
    let session = Session; 
    let prepared = prepare!(
     r#"insert into blah (id, name, reading) values (?, ?, ?)"#, 
     i32, String, f32); 
    // Don't use .to_string() for &str -> String; it's horribly inefficient. 
    let stmt = prepared((1, "test".to_owned(), 3.1)); 
    session.execute(stmt); 
} 

而这里的main功能扩展什么,给你一个参照系:

fn main() { 
    let session = Session; 
    let prepared = { 
     fn prepared_statement(args: (i32, String, f32)) -> Statement { 
      let statement = Statement::new(
       r#"insert into blah (id, name, reading) values (?, ?, ?)"#, 
       <[()]>::len(&[(),(),()])); 
      <f32 as BindArgument>::bind(
       <String as BindArgument>::bind(
        <i32 as BindArgument>::bind(
         statement, 0, args.0), 
        1, args.1), 
       2, args.2) 
     } 
     prepared_statement 
    }; 
    // Don't use .to_string() for &str -> String; it's horribly inefficient. 
    let stmt = prepared((1, "test".to_owned(), 3.1)); 
    session.execute(stmt); 
} 
+0

我原本是用原生的类型写的,但是希望int-> bind_int很容易破解,而不必明确支持每种类型。尽管使用本地类型,但完全没问题。谢谢你这个令人难以置信的答案。 –

+0

由于我将解析并验证查询语句后面的语句,我最好直接去编译器插件吗?我已经有解析器大部分完成了,所以我在添加宏后立即添加它。 –

+1

@JonHaddad编译器插件的问题是它只能与夜间编译器一起使用。一种可能性是在常规库中拥有“准备!”宏,这是一个编译器插件,它也在单独的库中进行验证,并在第三个库中共享验证码。然后,非夜间用户仍然可以使用您的库,但会产生一些运行时间开销。 –