Rocket, rusqlite, dynamic parameters & lifetimes

I'm trying to port some code to Rust and have run into a problem. The issue I have is that my function (a Rocket get handler) takes in two parameters, one mandatory and one optional, and it needs to turn this into a query for rusqlite.

This is the function:

#[get("/mkacc_get?<username>&<realname>")]
fn make_account_get(shstate: State<'_, SharedState>,
    username: String, realname: Option<String>) -> JsonValue {
  let state = shstate.lock().expect("shared state lock");

  let mut names = Vec::new();
  let mut params = Vec::new();
  let mut vals: Vec<&dyn ToSql> = Vec::new();

  names.push("username".to_string());
  params.push("?".to_string());
  vals.push(&username);

  if let Some(realname) = realname {
    names.push("realname".to_string());
    params.push("?".to_string());
    vals.push(&realname);
  }

  let q = format!("INSERT INTO {} ({})", strutil::join(&names, ","),
      strutil::join(&params, ","));
  match state.conn.execute(&q, &vals[..]) {
    Ok(_) => {
      json!({ "success": true, "id": state.conn.last_insert_rowid() })
    }
    Err(_) => {
      json!({ "success": false, "reason": "db error" })
    }
  }
}

The problem is obvious: Once I've realized that I have a realname I want to add it to the vals vector, but realname exits the scope very soon after, so due to lifetime issues I can't do that. But it's not clear to me how one would go about solving this. Is there a way to get a reference to the value within the Option without needing to isolate it in a new scope?

Taking a step back and completely ignoring how I'm trying to do it -- how would you go about performing dynamic-number-of-parameter queries using rusqlite?

You can use Option::as_ref to borrow your Option<String> into a Option<&String>.

if let Some(realname) = realname.as_ref() {
    names.push("realname".to_string());
    params.push("?".to_string());
    vals.push(realname); // no & needed as realname is already a reference
}

You can also use the fact that destructuring a reference to an enum works fine as if the fields were references:

if let Some(realname) = &realname {
    names.push("realname".to_string());
    params.push("?".to_string());
    vals.push(realname); // no & needed as realname is already a reference
}

An alternative to dynamic number of parameters would be to explicitly insert NULL into the realname field.

2 Likes

I think I get your point but may you reword what you are saying here?

Sure. In the example below, &realname is not an Option<String>, it's an &Option<String>. Yet, we are still allowed to use the pattern Some(realname) to get a &String out of it. This is called destructuring a reference, and it is a convenience feature that has not always been available called match ergonomics.

2 Likes