I am a Rust beginner and was wondering how to access a struct's fields dynamically:
use std::collections::HashMap;
struct User {
email: String,
name: String,
}
impl User {
fn new(attributes: &HashMap<String,String>) -> User {
let mut model = User {
email: "",
name: "",
};
for (attr_name,attr_value) in attributes {
// assign value "attr_value" to attribute "attr_name"
// no glue how to do this
// in php would be: $model->{$attr_name} = $attr_value;
model.*attr_name *= attr_value;
}
model;
}
}
fn main() {
let mut map: HashMap::new();
map.insert("email",String::from("foo#bar.de"));
map.insert("name",String::from("John doe"));
user_model = User::new(&map);
println!("{:?}",user_model);
}
How it is possible to initialize a struct by given HashMap?
Unless you change your User to contain a HashMap then Rust can't do that kind of "magic" (or it will require some proc macro usage, which is not beginner friendly).
Instead you can use a match, and match all the keys and update the User fields:
for (attr_name, attr_value) in attributes {
match attr_name {
"email" => model.email = attr_value.clone(),
"name" => model.name = attr_value.clone(),
_ => {}
}
}
However, instead of having empty Strings, I'd suggest using Option<String>.
struct User {
email: Option<String>,
name: Option<String>,
}
Then you can simplify your whole new method to just:
fn new(attributes: &HashMap<String, String>) -> User {
User {
email: attributes.get("email").cloned(),
name: attributes.get("name").cloned(),
}
}
Since you have some mixed String and &'static str usage, along with Debug not being implemented. Then here is the complete example:
use std::collections::HashMap;
#[derive(Debug)]
struct User {
email: Option<String>,
name: Option<String>,
}
impl User {
fn new(attributes: &HashMap<String, String>) -> User {
User {
email: attributes.get("email").cloned(),
name: attributes.get("name").cloned(),
}
}
}
fn main() {
let mut map = HashMap::new();
map.insert(String::from("email"), String::from("foo#bar.de"));
map.insert(String::from("name"), String::from("John doe"));
let user_model = User::new(&map);
println!("{:?}", user_model);
}
Related
I would like to filter into an iterable that contain a struct of Person with map() instruction but I don't know if it's possible on this way?
I would like to know which is the bast way to complete this task properly in Rust.
// This my structure of objet Person
#[derive(Debug, Clone)]
struct Person {
product_id: i64,
nom: String,
prenom: String,
email: Option<String>,
actif: bool,
}
// Main function which create a person and add this person into a HashMap<i64, Person>
fn main() {
// create list of person
let mut personnes = Personnes::new();
// Create one person
let person = Person {
product_id: 1,
nom: String::from("TestNom"),
prenom: String::from("TestPrenom"),
email: Some("test#mail.com".to_string()),
actif: true,
};
// Add person into my iterable
personnes.add(person);
// Add few persons...
// Imagine multiple Person and find person who actif = true
persons_actives(&personnes);
}
// Something like that :
fn persons_actives(personnes: &Personnes) {
let persons_actives = personnes
.inner
.iter()
.map(|person.actif| person.actif == true)
.collect();
}
// But it's maybe impossible in this way ?
I tried :
fn persons_actives(personnes: &Personnes) {
let persons_actives = personnes.inner.iter().filter(|person| person.actif == true).collect();
}
and :
fn persons_actives(personnes: &Personnes) {
let persons_actives = personnes.inner.iter().find(|person| person.actif == true).collect();
}
but i have the same error :
"no field actif on type &(&i64, &Person)"
You can iterate through your HashMap values, then filter() and collect() the result into a Vector.
use std::collections::HashMap;
#[derive(Debug)]
struct Person {
is_active: bool
//other params
}
fn main() {
// define your map
let mut map :HashMap<i32,Person> = HashMap::new();
// init a Person
let person: Person = Person{
is_active: true
// set other params
};
// insert it to map
map.insert(1,person);
// filter person.is_active == true
let active_persons : Vec<_> = map.values().filter(|person| person.is_active==true).collect();
// result
println!("{:?}",active_persons);
}
Now you have your desired result in active_persons.
Thanks for your reply,
I have tested this one :
let active_persons : Vec<_> = personnes.inner.values().filter(|&person| if_else!(person.actif, true, false)).collect();
with ternary-rs library for the if_else syntax.
Your right i shold have noticed the structiure of Personnes which is :
struct Personnes {
inner: HashMap<i64, Person>,
}
But i have keep your Vec<_> Structure for the active person.
I created two persons, one is active and the other not and it's worked !
Thanks a lot !
For the given structs:
#[derive(BorshDeserialize, BorshSerialize)]
struct MarketOption {
proposal_id: u64,
}
#[derive(BorshDeserialize, BorshSerialize)]
struct Market {
description: String,
market_options: Vector<MarketOption>,
}
#[near_bindgen]
#[derive(BorshDeserialize, BorshSerialize)]
pub struct MarketFactory {
markets: HashMap<String, Market>,
}
First, to initialize storage:
#[near_bindgen]
impl MarketFactory {
#[init]
pub fn new(dao_account_id: AccountId) -> Self {
Self {
markets: HashMap::default(), // <== IS THIS CORRECT? This should be populated in the future
}
}
Then, when getting the data, I'm looking to insert into markets with an empty Vector:
pub fn create_market(&mut self, description: String, market_options: Vector<String>) -> String {
let market_id = self.get_random_market_id();
self.markets.insert(
market_id,
Market {
description,
market_options: Vector::new(b"m"),
},
);
self.process_market_options(&market_id, market_options);
And then, process each market_options to get an ID u64 which should be pushed into Market.market_options:
self.markets
.entry(market_id). // <== GET the MARKET
.market_options // <== GET its market_options, NOT WORKING
.push(MarketOption { proposal_id: id }); // <== PUSH an ID that comes from a promise
Also, should I use HashMap or LookupMap? Vector or Vec?
I am using the HashMap to store some of the value. Now I want to check whether HashMap contains keys, if not insert data else return HashMap is not empty. Below code explain that I am checking for product id, but I want to check for 2 keys product key and product url.
use std::collections::hash_map::Entry::Vacant;
pub struct Products<DB>
where
DB: DatabaseProvider,
{
database: DB,
products: HashMap<String, Product>,
}
pub struct Product {
pub product_id: String,
pub created: String,
pub product_description: String,
pub product_url: String,
}
pub async fn get_product_store_hashmap(&mut self) -> Result<()>
{
// calling the api here, once response is received, store in hashmap
let product = Product {
product_id: somedata,
created: somedata,
product_description: somedata,
product_url:somedata,
};
self.products.insert(product_id.clone(), product);
}
pub async fn insertProduct(&mut self, product:Product) -> Result<()> {
// How to check two keys are contains value.
if let Vacant(entry:Vacant::entry<(string,Product)) = self.product.entry(product_id) {
// insert the data
} else {
// retun no product id found
}
}
You can use map.contains_key to test.
Example:
fn insertProduct(map: &mut HashMap<String, Product>, product: Product) -> Result<()> {
if map.contains_key(product.product_id) || map.contains_key(product.product_url) {
Err()
} else {
map.insert(product.product_id, Product);
Ok(())
}
}
You can use Vacant and Occupied to check if key already exists or not and return result according using match
use std::collections::HashMap;
use std::collections::hash_map::Entry::{Vacant, Occupied};
use std::io::ErrorKind;
fn main() {
let mut h: HashMap<&str, u8> = HashMap::new();
let value: &str = "a";
// h.entry(value).or_insert(1); // <- uncomment to get err result
let result = match h.entry(value) {
Vacant(entry) => {
entry.insert(1);
Ok(())
},
Occupied(_) => Err(ErrorKind::AlreadyExists)
};
println!("{:?}", result);
}
Playground
I'm just starting to learn Rust and built a sample api project using Rocket and rusqlite. I want to get all of the items in my database through a get endpoint. My table has an id field and a string which is a list of ids seperated by ,. After getting this back I want to split this string and go back to the database for each entry and then create a struct for it. I'm having trouble figuring this out in a functional way for rust. Does anyone have any advice for how to proceed? I want to manipulate the data myself and avoid orm tools like diseil.
data.rs
use serde::{Deserialize, Serialize};
#[derive(Serialize)]
pub struct Item {
pub id: i32,
pub time_tp_prepare: i32,
pub name: String
}
#[derive(Serialize)]
pub struct Table {
pub id: i32,
pub items: Vec<Item>
}
#[derive(Serialize)]
pub struct StatusMessage {
pub message: String
}
database.rs
use rusqlite::Result;
pub struct ItemData {
pub id: i32,
pub time_tp_prepare: i32,
pub name: String
}
pub struct TableData {
pub id: i32,
pub itemIds: String
}
pub fn setup_db() -> Result<String, String>{
let db_connection = match rusqlite::Connection::open("data.sqlite") {
Ok(connection) => connection,
Err(_) => {
return Err("Cannot connect to database.".into());
}
};
match db_connection
.execute(
"create table if not exists item (
id integer primary key,
name varchar(64) not null,
preperation_time integer not null
);
create table if not exists restaurant_table (
id integer primary key,
items varchar(64) not null",
[]
) {
Ok(success) => Ok("Successfully created database tables.".into()),
Err(_) => return Err("Could not run create table sql".into())
}
}
main.rs
#![feature(proc_macro_hygiene, decl_macro)]
#[macro_use]
extern crate rocket;
use rocket_contrib::json::Json;
use rusqlite::Result;
mod database;
mod data;
#[get("/api/get-all-tables-v1")]
fn get_all_tables() -> Result<Json<data::Table>, String> {
let db_connection = match rusqlite::Connection::open("data.sqlite") {
Ok(connection) => connection,
Err(_) => {
return Err("Cannot connect to database.".into());
}
};
let mut statement = match db_connection.prepare("select id, items from restaurant_table;") {
Ok(statement) => statement,
Err(_) => return Err("Failed to prepare query.".into())
};
let results = statement.query_map([], |row| {
Ok(database::TableData {
id: row.get(0)?,
itemIds: row.get(1)?
})
});
match results {
Ok(rows) => {
///// This is where I'm stuck on what to do next //////
let collection: rusqlite::Result<Vec<data::Item>> = rows.collect();
match collection {
Ok(items) => Ok(Json(data::Table { items })),
Err(_) => Err("Could not collect items.".into())
}
},
Err(_) => Err("Failed to fetch items.".into())
}
}
fn main() {
match database::setup_db() {
Ok(_) => luanch_server(),
Err(error) => eprintln!("Program failed to start because of Error {}.", error)
}
}
fn luanch_server() {
rocket::ignite().mount("/", routes![get_all_tables]).launch();
}
I figured it out and I will post the code here in case anyone finds it in the future. It is not great code and I ended up not using it but it was a good learning experience.
for row in rows.into_iter().flatten() {
let ids = row.itemIds.split(",");
for id in ids {
let mut item_statement = db_connection
.prepare("select * from item where id = :id;")
.expect("Failed to prepare query.");
let mut item_rows = item_statement
.query_named(rusqlite::named_params!{ ":id": id })
.expect("Select item statement failed");
while let Some(item_row) = item_rows
.next()
.expect("Row Failed.") {
let item = data::Item {
id: row.get(0),
time_to_prepare: row.get(1),
name: row.get(2)
};
if !tables.contains(item.id) {
tables.insert(item);
}
}
I am not able to print values from a Vec from a module, could you please help?
Here is the content of the function in the module:
pub struct Author {
_id: i32,
name: String,
country: String
}
pub fn get_saved_records() -> Result<Vec<Author>,Error>{
let mut client = Client::connect("postgresql://***:***#localhost/****",NoTls)?;
let records = client.query("SELECT * FROM author",&[])?;
let mut the_records : Vec<Author> = vec![];
for record in records{
let author = Author {
_id: record.get(0),
name: record.get(1),
country: record.get(2),
};
the_records.push(author);
}
Ok(the_records)
}
Here is the main file where I call get_saved_records() function:
match db::get_saved_records(&mut conn){
Ok(records) => {
for record in records{
println!("{}",record.name);
}
},
Err(_) => println!("No record found")
}
I am getting the error message: field name of struct db::Author is private.
Thank you.
The members of your struct need to be declared pub in order to be directly accessible.
Here, only the name member is accessible.
pub struct Author {
_id: i32,
pub name: String,
country: String
}
Note that it makes sense for a struct to be public while having its
members private.
This ensures encapsulation: the members of the struct are only accessible
from the methods of its implementation (and the surrounding code in the module).
Of course, those methods can also be made public or private.