2015-10-16 61 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編譯器插件的問題是它只能與夜間編譯器一起使用。一種可能性是在常規庫中擁有「準備!」宏,這是一個編譯器插件,它也在單獨的庫中進行驗證,並在第三個庫中共享驗證碼。然後,非夜間用戶仍然可以使用您的庫,但會產生一些運行時間開銷。 –