The rustc-serialize package for Rust allows a struct to be serialized or deserialized to JSON automatically in some cases by deriving from RustcEncodable and RustcDecodable, respectively. For example, the following struct:
#[derive(RustcEncodable, RustcDecodable)]
struct Person {
first_name: String,
last_name: String,
age: u8,
}
would be represented as this JSON:
{
"first_name": "Joe",
"last_name": "Shmoe",
"age": 30
}
I need to deserialize some JSON that uses camel-cased keys. This seems to require that the struct fields be named similarly, which emits a compiler warning that struct fields should be snake-cased. I can silence this warning with #[allow(non_snake_case)], but I'd rather have the struct field snake-cased. Is there a way to do this without manually implementing JSON serialization/deserialization with the ToJson/Encodable/Decodable traits?
No, the #[derive] infrastructure provides no opportunities for customisation at all.
You can, however, know what the #[derive] stuff expands to with rustc -Z unstable-options --pretty expanded:
#![feature(no_std)]
#![no_std]
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std as std;
struct Person {
first_name: String,
last_name: String,
age: u8,
}
#[automatically_derived]
impl ::rustc_serialize::Decodable for Person {
fn decode<__D: ::rustc_serialize::Decoder>(__arg_0: &mut __D)
-> ::std::result::Result<Person, __D::Error> {
__arg_0.read_struct("Person", 3usize, |_d| -> _ {
::std::result::Result::Ok(Person{first_name:
match _d.read_struct_field("first_name",
0usize,
::rustc_serialize::Decodable::decode)
{
::std::result::Result::Ok(__try_var)
=>
__try_var,
::std::result::Result::Err(__try_var)
=>
return ::std::result::Result::Err(__try_var),
},
last_name:
match _d.read_struct_field("last_name",
1usize,
::rustc_serialize::Decodable::decode)
{
::std::result::Result::Ok(__try_var)
=>
__try_var,
::std::result::Result::Err(__try_var)
=>
return ::std::result::Result::Err(__try_var),
},
age:
match _d.read_struct_field("age",
2usize,
::rustc_serialize::Decodable::decode)
{
::std::result::Result::Ok(__try_var)
=>
__try_var,
::std::result::Result::Err(__try_var)
=>
return ::std::result::Result::Err(__try_var),
},}) })
}
}
#[automatically_derived]
impl ::rustc_serialize::Encodable for Person {
fn encode<__S: ::rustc_serialize::Encoder>(&self, __arg_0: &mut __S)
-> ::std::result::Result<(), __S::Error> {
match *self {
Person {
first_name: ref __self_0_0,
last_name: ref __self_0_1,
age: ref __self_0_2 } =>
__arg_0.emit_struct("Person", 3usize, |_e| -> _ {
match _e.emit_struct_field("first_name",
0usize, |_e| -> _ {
(*__self_0_0).encode(_e)
}) {
::std::result::Result::Ok(__try_var) =>
__try_var,
::std::result::Result::Err(__try_var) =>
return ::std::result::Result::Err(__try_var),
};
match _e.emit_struct_field("last_name",
1usize, |_e| -> _ {
(*__self_0_1).encode(_e)
}) {
::std::result::Result::Ok(__try_var) =>
__try_var,
::std::result::Result::Err(__try_var) =>
return ::std::result::Result::Err(__try_var),
};
return _e.emit_struct_field("age", 2usize,
|_e| -> _ {
(*__self_0_2).encode(_e)
}); }),
}
}
}
Yeah, it’s pretty clumsy, all expanded like that, but it can be collapsed quite easily and altered to suit:
impl Decodable for Person {
fn decode<D: Decoder>(decoder: &mut D) -> Result<Person, D::Error> {
decoder.read_struct("Person", 3, |d| Ok(Person {
first_name: try!(d.read_struct_field("firstName", 0, Decodable::decode)),
last_name: try!(d.read_struct_field("lastName", 1, Decodable::decode)),
age: try!(d.read_struct_field("age", 2, Decodable::decode)),
}))
}
}
impl Encodable for Person {
fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
s.emit_struct("Person", 3, |e| {
try!(e.emit_struct_field("firstName", 0, |e| self.first_name.encode(e)));
try!(e.emit_struct_field("lastName", 1, |e| self.last_name.encode(e)));
e.emit_struct_field("age", 2, |e| self.age.encode(e))
})
}
}
Related
I have a Companion trait that encompasses a base trait for Components such as Health. I store a list of Companion using trait objects because all companions must at least implement the Companion trait. However not all companions will use the subtype Health trait.
Now the heal command only accepts a list of Health traits, so I need to filter out, remap and downcast all the base Companion traits so that it supports the Health traits.
I understand this is bad design. How can I implement the same behavior without having to downcast the trait objects to a specific component? Note: I cannot have a large struct which includes all the subtypes into one type.
Here's the code I have so far:
type CompanionId = Uuid;
fn main() {
let mut companion_storage: HashMap<CompanionId, Box<dyn Companion>> = HashMap::new();
let companion_id: CompanionId = Uuid::new_v4();
companion_storage.insert(
companion_id,
Box::new(Cheetah {
...
}),
);
let mut player = Player {
...,
companions: Vec::new(),
};
player.companions.push(companion_id);
'GameLoop: loop {
let input = poll_input().trim().to_lowercase();
match input.as_str() {
// TODO: Extract healing component here.
"heal" => heal_command(companion_id, companion_storage.into_iter().filter(|(companion_id, companion)| {
// QUESTION: How do I filter out Companions without the Health trait here so they can automatically be downcasted and mapped?
}).collect()),
"q" => {
break 'GameLoop;
}
"s" => {
status_command(&player, &companion_storage); // SAME PROBLEM HERE
}
_ => println!("Unknown command"),
}
}
}
struct Player {
id: u8,
name: String,
companions: Vec<CompanionId>,
}
trait Companion {
...
}
trait Health: Companion {
...
}
trait Status: Health {}
struct Cheetah {
id: CompanionId,
name: String,
current_health: f32,
max_health: f32,
}
impl Companion for Cheetah {
...
}
impl Health for Cheetah {
...
}
fn heal_command(
companion_id: CompanionId,
companion_storage: &mut HashMap<CompanionId, Box<dyn Health>>,
) {
let companion = companion_storage.get_mut(&companion_id).unwrap();
companion.heal_max();
println!("Healed to max.");
}
fn status_command(player: &Player, companion_storage: &mut HashMap<CompanionId, Box<dyn Status>>) {
println!("Status for {}: ", player.name);
println!("===============================");
print!("Companions: ");
for companion_id in &player.companions {
let companion = companion_storage.get(companion_id).unwrap();
print!(
"{} [{}/{}], ",
companion.name(),
companion.health(),
companion.max_health()
);
}
println!();
println!("===============================");
}
Is this code a better alternative?
type CompanionId = Uuid;
fn main() {
let mut companion_storage: HashMap<CompanionId, Companion> = HashMap::new();
let companion_id: CompanionId = Uuid::new_v4();
companion_storage.insert(
companion_id,
Companion {
id: companion_id,
name: "Cheetah".to_string(),
health: Some(Box::new(RegularHealth {
current_health: 50.0,
max_health: 50.0,
})),
},
);
let mut player = Player {
id: 0,
name: "FyiaR".to_string(),
companions: Vec::new(),
};
player.companions.push(companion_id);
'GameLoop: loop {
let input = poll_input().trim().to_lowercase();
match input.as_str() {
// TODO: Extract healing component here.
"heal" => {
let companion = companion_storage.get_mut(&companion_id).unwrap();
match companion.health_mut() {
None => {
println!("The selected companion doesn't have health associated with it.");
}
Some(health) => {
heal_command(health);
println!("{} was healed to max.", companion.name);
}
}
}
"q" => {
break 'GameLoop;
}
"s" => {
status_command(&player, &companion_storage); // SAME PROBLEM HERE
}
_ => println!("Unknown command"),
}
}
}
struct Player {
id: u8,
name: String,
companions: Vec<CompanionId>,
}
struct Companion {
id: CompanionId,
name: String,
health: Option<Box<dyn Health>>,
}
struct RegularHealth {
current_health: f32,
max_health: f32,
}
trait Health {
...
}
impl Companion {
fn health_mut(&mut self) -> Option<&mut dyn Health> {
match self.health.as_mut() {
None => None,
Some(health) => Some(health.as_mut()),
}
}
fn health(&self) -> Option<&dyn Health> {
match self.health.as_ref() {
None => None,
Some(health) => Some(health.as_ref()),
}
}
}
impl Health for RegularHealth {
...
}
fn heal_command(health: &mut dyn Health) {
health.heal_max();
}
fn status_command(player: &Player, companion_storage: &HashMap<CompanionId, Companion>) {
println!("Status for {}: ", player.name);
println!("===============================");
print!("Companions: ");
for companion_id in &player.companions {
let companion = companion_storage.get(companion_id).unwrap();
match companion.health.as_ref() {
None => {}
Some(health) => {
print!(
"{} [{}/{}], ",
companion.name,
health.health(),
health.max_health()
);
}
}
}
println!();
println!("===============================");
}
I would like to deserialize a wire format, like this JSON, into the Data structure below and I am failing to write the serde Deserialize implementations for the corresponding rust types.
{ "type": "TypeA", "value": { "id": "blah", "content": "0xa1b.." } }
enum Content {
TypeA(Vec<u8>),
TypeB(BigInt),
}
struct Value {
id: String,
content: Content,
}
struct Data {
typ: String,
value: Value,
}
The difficulty is selecting the correct value of the Content enumeration, which is based on the typ value.
As far as I know, deserialization in serde is stateless, an hence there is no way of either
knowing what the value of typ is at the time of deserialization of content (even though the deserialization order is guaranteed)
or injecting the value of typ in the deserializer then collecting it.
How can this be achieved with serde ?
I have looked at
serde_state but I cannot get the macros working and this library is wrapping serde, which worries me
DeserializeSeed but my undestanding is that it must be used in place of Deserialize for all types and my data model is big
The existing SO answers usually exploit the fact that the related fields are at the same level. This is not the case here: the actual data model is big, deep and the fields are "far apart"
Much simpler using tagging, but changing your data structure:
use serde::{Deserialize, Deserializer}; // 1.0.130
use serde_json; // 1.0.67
#[derive(Debug, Deserialize)]
#[serde(tag = "type", content = "value")]
enum Data {
TypeA(Value<String>),
TypeB(Value<u32>),
}
#[derive(Debug, Deserialize)]
struct Value<T> {
id: String,
content: T,
}
fn main() {
let input = r#"{"type": "TypeA", "value": { "id": "blah", "content": "0xa1b..."}}"#;
let data: Data = serde_json::from_str(input).unwrap();
println!("{:?}", data);
}
Playground
Also, you can write your own custom desializer using some intermediary serde_json::Value:
use serde::{Deserialize, Deserializer};// 1.0.130
use serde_json; // 1.0.67
#[derive(Debug)]
enum Content {
TypeA(String),
TypeB(String),
}
#[derive(Debug)]
struct Value {
id: String,
content: Content,
}
#[derive(Debug)]
struct Data {
typ: String,
value: Value,
}
impl<'de> Deserialize<'de> for Data {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let json: serde_json::value::Value = serde_json::value::Value::deserialize(deserializer)?;
let typ = json.get("type").expect("type").as_str().unwrap();
let value = json.get("value").expect("value");
let id = value.get("id").expect("id").as_str().unwrap();
let content = value.get("content").expect("content").as_str().unwrap();
Ok(Data {
typ: typ.to_string(),
value: Value {
id: id.to_string(),
content: {
match typ {
"TypeA" => Content::TypeA(content.to_string()),
"TypeB" => Content::TypeB(content.to_string()),
_ => panic!("Invalid type, but this should be an error not a panic"),
}
}
}
})
}
}
fn main() {
let input = r#"{"type": "TypeA", "value": { "id": "blah", "content": "0xa1b..."}}"#;
let data: Data = serde_json::from_str(input).unwrap();
println!("{:?}", data);
}
Playground
Disclaimer: I didn't handle error correctly and you could also extract the content matching into a function for example. The above code is just to illustrate the main idea.
There's a few different ways this can be solved, e.g. with a custom impl Deserialize for Data, then deserialize into a serde_json::Value, and then manually juggling between the types.
For a somewhat example of that, checkout this answer that I wrote in the past. It's not a one-to-one solution, but it might give some hints for implementing Deserialize manually, for what you want.
That being said. Personally, I prefer to minimize when I have to impl Deserialize manually, and instead deserialize into another type, and have it automatically convert using #[serde(from = "FromType")].
First, instead of type_: String, I'd suggest we introduce enum ContentType.
#[derive(Deserialize, Clone, Copy, Debug)]
enum ContentType {
TypeA,
TypeB,
TypeC,
TypeD,
}
Now, let's consider the types you introduced. I've added a few extra variants to Content, as you mentioned the variants can be different.
#[derive(Deserialize, Debug)]
#[serde(untagged)]
enum Content {
TypeA(Vec<u8>),
TypeB(Vec<u8>),
TypeC(String),
TypeD { foo: i32, bar: i32 },
}
#[derive(Deserialize, Debug)]
struct Value {
id: String,
content: Content,
}
#[derive(Deserialize, Debug)]
#[serde(try_from = "IntermediateData")]
struct Data {
#[serde(alias = "type")]
type_: ContentType,
value: Value,
}
Nothing crazy yet or much different. All the "magic" happens in the IntermediateData type, along with the impl TryFrom.
First, let's introduce a check_type(), which takes a ContentType and checks it against the Content. If the Content variant doesn't match the ContentType variant, then convert it.
In short, when using #[serde(untagged)] then when serde attempts to deserialize Content it will always return the first successful variant it can deserialize to if any. So if it can deserialize a Vec<u8>, then it will always result in Content::TypeA(). Knowing this, then in our check_type(), if the ContentType is TypeB and the Content is TypeA. Then we simply change it to TypeB.
impl Content {
// TODO: impl proper error type instead of `String`
fn check_type(self, type_: ContentType) -> Result<Self, String> {
match (type_, self) {
(ContentType::TypeA, content # Self::TypeA(_)) => Ok(content),
(ContentType::TypeB, Self::TypeA(content)) => Ok(Self::TypeB(content)),
(ContentType::TypeC | ContentType::TypeD, content) => Ok(content),
(type_, content) => Err(format!(
"unexpected combination of {:?} and {:?}",
type_, content
)),
}
}
}
Now all we need is the intermediate IntermediateData, along with a TryFrom conversion, which calls check_type() on the Content.
#[derive(Deserialize, Debug)]
struct IntermediateData {
#[serde(alias = "type")]
type_: ContentType,
value: Value,
}
impl TryFrom<IntermediateData> for Data {
// TODO: impl proper error type instead of `String`
type Error = String;
fn try_from(mut data: IntermediateData) -> Result<Self, Self::Error> {
data.value.content = data.value.content.check_type(data.type_)?;
Ok(Data {
type_: data.type_,
value: data.value,
})
}
}
That's all. Now we can test it against the following:
// serde = { version = "1", features = ["derive"] }
// serde_json = "1.0"
use std::convert::TryFrom;
use serde::Deserialize;
// ... all the previous code ...
fn main() {
let json = r#"{ "type": "TypeA", "value": { "id": "foo", "content": [0, 1, 2, 3] } }"#;
let data: Data = serde_json::from_str(json).unwrap();
println!("{:#?}", data);
let json = r#"{ "type": "TypeB", "value": { "id": "foo", "content": [0, 1, 2, 3] } }"#;
let data: Data = serde_json::from_str(json).unwrap();
println!("{:#?}", data);
let json = r#"{ "type": "TypeC", "value": { "id": "bar", "content": "foo" } }"#;
let data: Data = serde_json::from_str(json).unwrap();
println!("{:#?}", data);
let json = r#"{ "type": "TypeD", "value": { "id": "baz", "content": { "foo": 1, "bar": 2 } } }"#;
let data: Data = serde_json::from_str(json).unwrap();
println!("{:#?}", data);
}
Then it correctly results in Datas with Content::TypeA, Content::TypeB, Content::TypeC, and the last one Content::TypeD.
Lastly. There is issue #939 which talks about adding a #[serde(validate = "...")]. However, it was created in 2017, so I wouldn't hold my breath on it.
DeserializeSeed can be mixed with normal Deserialize code. It does not need to be used for all types. Here, it is enough to use it to deserialize Value.
Playground
use serde::de::{DeserializeSeed, IgnoredAny, MapAccess, Visitor};
use serde::*;
use std::fmt;
#[derive(Debug)]
enum ContentType {
A,
B,
Unknown,
}
#[derive(Debug)]
enum Content {
TypeA(String),
TypeB(i32),
}
#[derive(Debug)]
struct Value {
id: String,
content: Content,
}
#[derive(Debug)]
struct Data {
typ: String,
value: Value,
}
impl<'de> Deserialize<'de> for Data {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct DataVisitor;
impl<'de> Visitor<'de> for DataVisitor {
type Value = Data;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct Data")
}
fn visit_map<A>(self, mut access: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut typ = None;
let mut value = None;
while let Some(key) = access.next_key()? {
match key {
"type" => {
typ = Some(access.next_value()?);
}
"value" => {
let seed = match typ.as_deref() {
Some("TypeA") => ContentType::A,
Some("TypeB") => ContentType::B,
_ => ContentType::Unknown,
};
value = Some(access.next_value_seed(seed)?);
}
_ => {
access.next_value::<IgnoredAny>()?;
}
}
}
Ok(Data {
typ: typ.unwrap(),
value: value.unwrap(),
})
}
}
deserializer.deserialize_map(DataVisitor)
}
}
impl<'de> DeserializeSeed<'de> for ContentType {
type Value = Value;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
struct ValueVisitor(ContentType);
impl<'de> Visitor<'de> for ValueVisitor {
type Value = Value;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct Value")
}
fn visit_map<A>(self, mut access: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut id = None;
let mut content = None;
while let Some(key) = access.next_key()? {
match key {
"id" => {
id = Some(access.next_value()?);
}
"content" => {
content = Some(match self.0 {
ContentType::A => Content::TypeA(access.next_value()?),
ContentType::B => Content::TypeB(access.next_value()?),
ContentType::Unknown => {
panic!("Should not happen if type happens to occur before value, but JSON is unordered.");
}
});
}
_ => {
access.next_value::<IgnoredAny>()?;
}
}
}
Ok(Value {
id: id.unwrap(),
content: content.unwrap(),
})
}
}
deserializer.deserialize_map(ValueVisitor(self))
}
}
fn main() {
let j = r#"{"type": "TypeA", "value": {"id": "blah", "content": "0xa1b.."}}"#;
dbg!(serde_json::from_str::<Data>(j).unwrap());
let j = r#"{"type": "TypeB", "value": {"id": "blah", "content": 666}}"#;
dbg!(serde_json::from_str::<Data>(j).unwrap());
let j = r#"{"type": "TypeB", "value": {"id": "blah", "content": "Foobar"}}"#;
dbg!(serde_json::from_str::<Data>(j).unwrap_err());
}
The main downside of this solution is that you lose the possibility of deriving the code.
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 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);
}
In Rust, how should one go about grouping related structs so that a function signature can accept multiple different types while refering to the concrete type inside the method body?
The following example is contrived for simplicity:
enum Command {
Increment {quantity: u32},
Decrement {quantity: u32},
}
fn process_command(command: Command) {
match command {
Command::Increment => increase(command),
Command::Decrement => decrease(command),
};
}
fn increase(increment: Command::Increment) {
println!("Increasing by: {}.", increment.quantity);
}
fn decrease(decrement: Command::Decrement) {
println!("Decreasing by: {}.", decrement.quantity);
}
fn main() {
let input = "Add";
let quantity = 4;
let command: Command = match input {
"Add" => Command::Increment { quantity: quantity },
"Subtract" => Command::Decrement { quantity: quantity },
_ => unreachable!(),
};
process_command(command);
}
Compiling results in the following two errors:
src/main.rs:13:24: 13:42 error: found value name used as a type: DefVariant(DefId { krate: 0, node: 4 }, DefId { krate: 0, node: 5 }, true) [E0248]
src/main.rs:13 fn increase(increment: Command::Increment) {
^~~~~~~~~~~~~~~~~~
src/main.rs:17:24: 17:42 error: found value name used as a type: DefVariant(DefId { krate: 0, node: 4 }, DefId { krate: 0, node: 8 }, true) [E0248]
src/main.rs:17 fn decrease(decrement: Command::Decrement) {
^~~~~~~~~~~~~~~~~~
error: aborting due to 2 previous errors
If I declare the structs seperately, and wrap the structs within a tuple struct (correct terminology?) each within the enum then I get the expected result, but with the verbosity and similar type names all over the place I suspect that I've misunderstood someting:
struct Increment {
quantity: u32,
}
struct Decrement {
quantity: u32,
}
enum Command {
Increment(Increment),
Decrement(Decrement),
}
fn process_command(command: Command) {
match command {
Command::Increment(increment) => increase(increment),
Command::Decrement(decrement) => decrease(decrement),
};
}
fn increase(increment: Increment) {
println!("Increasing by: {}.", increment.quantity);
}
fn decrease(decrement: Decrement) {
println!("Decreasing by: {}.", decrement.quantity);
}
fn main() {
let input = "Add";
let quantity = 4;
let command: Command = match input {
"Add" => Command::Increment(Increment { quantity: quantity }),
"Subtract" => Command::Decrement(Decrement { quantity: quantity }),
_ => unreachable!(),
};
process_command(command);
}
Running outputs:
Increasing by: 4.
Is wrapping the struct within an enum type (terminology?) sharing the same name really the best solution? Command::Increment(Increment { quantity: 7 })
Yes, this is the best you will get along this line of implementation. An enum is one type only; its variants are purely that—variants, not types.
Another alternative is to use a trait and generics:
struct Increment {
quantity: u32,
}
struct Decrement {
quantity: u32,
}
trait Command {
fn process(self);
}
impl Command for Increment {
fn process(self) {
println!("Increasing by {}", self.quantity);
}
}
impl Command for Decrement {
fn process(self) {
println!("Decreasing by {}", self.quantity);
}
}
Of course, it’s not a direct parallel; if you want to store a command of potentially differing types you’ll need to change process to take self: Box<Self> or &self, and you’ll need to work with either Box<Command> or &Command, but it’s another way of doing things that may suit your requirements. And as far as the definitions are concerned, it’s purer.
I may be misunderstanding your simple example, but remember that you can implement methods on enums directly:
enum Command {
Increment {quantity: u32},
Decrement {quantity: u32},
}
impl Command {
fn process(self) {
match self {
Command::Increment { quantity } => {
println!("Increasing by: {}.", quantity)
},
Command::Decrement { quantity } => {
println!("Decreasing by: {}.", quantity)
},
};
}
}
fn main() {
let input = "Add";
let quantity = 4;
let command: Command = match input {
"Add" => Command::Increment { quantity: quantity },
"Subtract" => Command::Decrement { quantity: quantity },
_ => unreachable!(),
};
command.process();
}
I happen to like this version because it eliminates the redundancy of process_command(command).
What about this one, I am not sure I really understood your issue
enum Command {
Increment (u32),
Decrement (u32),
}
fn process_command(command: Command) {
match command {
Command::Increment(quantity) => increase(quantity),
Command::Decrement(quantity) => decrease(quantity),
};
}
fn increase(quantity: u32) {
println!("Increasing by: {}.", quantity);
}
fn decrease(quantity: u32) {
println!("Decreasing by: {}.", quantity);
}
fn main() {
let input = "Add";
let quantity = 4;
let command: Command = match input {
"Add" => Command::Increment (quantity),
"Subtract" => Command::Decrement (quantity),
_ => unreachable!(),
};
process_command(command);
}