Using derive syntax, can I implement traits like Hash or PartialEq using specific fields, not all of them?
It could look like:
#[derive(Debug, Hash, Eq, PartialEq)]
struct MyStruct {
id: i32,
name: String,
#[derive(hash_skip, eq_skip)]
aux_data1: f64,
#[derive(hash_skip, eq_skip)]
aux_data2: f64,
#[derive(hash_skip, eq_skip)]
aux_data3: String,
}
I want the hash method to only use id, and name and no others.
The serde library allows something like this for serialization.
No, there is no such feature in Rust at this moment. What I would suggest is to use the implementation for tuples available for these traits, like this:
use std::hash::{Hash, Hasher};
#[derive(Debug)]
struct MyStruct {
id: i32,
name: String,
aux_data1: f64,
aux_data2: f64,
aux_data3: String,
}
impl Hash for MyStruct {
fn hash<H>(&self, state: &mut H) where H: Hasher {
(&self.id, &self.name).hash(state);
}
}
impl PartialEq for MyStruct {
fn eq(&self, other: &Self) -> bool {
(&self.id, &self.name) == (&other.id, &other.name)
}
}
Edit: or as #Shepmaster suggested in a comment below, you can create a key function which returns a tuple of all useful fields and use it.
impl MyStruct {
fn key(&self) -> (&i32, &String) {
(&self.id, &self.name)
}
}
impl Hash for MyStruct {
fn hash<H>(&self, state: &mut H) where H: Hasher {
self.key().hash(state);
}
}
impl PartialEq for MyStruct {
fn eq(&self, other: &Self) -> bool {
self.key() == other.key()
}
}
This is possible by using derivative.
Example:
use derivative::Derivative;
#[derive(Derivative)]
#[derivative(Hash, PartialEq)]
struct Foo {
foo: u8,
#[derivative(Hash="ignore")]
#[derivative(PartialEq="ignore")]
bar: u8,
}
Related
I have a recursive data structure in my pet-project:
(this is a simplified example)
pub trait Condition {
fn validate(&self, s: &str) -> bool;
}
pub struct Equal {
ref_val: String,
}
impl Condition for Equal {
fn validate(&self, s: &str) -> bool { self.ref_val == s }
}
pub struct And<A, B> where A: Condition + ?Sized, B: Condition + ?Sized {
left: Box<A>,
right: Box<B>,
}
impl<A, B> Condition for And<A, B> where A: Condition + ?Sized, B: Condition + ?Sized {
fn validate(&self, s: &str) -> bool { self.left.validate(s) && self.right.validate(s) }
}
and i want to serialize and de-serialize the condition trait (using serde) eg.:
fn main() {
let c = And {
left: Box::new(Equal{ ref_val: "goofy".to_string() }),
right: Box::new(Equal{ ref_val: "goofy".to_string() }),
};
let s = serde_json::to_string(&c).unwrap();
let d: Box<dyn Condition> = serde_json::from_string(&s).unwrap();
}
Because serde cannot deserialize dyn traits out-of-the box, i tagged the serialized markup, eg:
#[derive(PartialEq, Debug, Serialize)]
#[serde(tag="type")]
pub struct Equal {
ref_val: String,
}
and try to implement a Deserializer and a Vistor for Box<dyn Condition>
Since i am new to Rust and because the implementation of a Deserializer and a Visitor is not that straightforward with the given documentation, i wonder if someone has an idea how to solve my issue with an easier approach?
I went through the serde documentation and searched for solution on tech sites/forums.
i tried out typetag but it does not support generic types
UPDATE:
To be more precise: the serialization works fine, ie. serde can serialize any concrete object of the Condition trait, but in order to deserialize a Condition the concrete type information needs to be provided. But this type info is not available at compile time. I am writing a web service where customers can upload rules for context matching (ie. Conditions) so the controller of the web service does not know the type when the condition needs to be deserialized.
eg. a Customer can post:
{"type":"Equal","ref_val":"goofy"}
or
{"type":"Greater","ref_val":"Pluto"}
or more complex with any combinator ('and', 'or', 'not')
{"type":"And","left":{"type":"Greater","ref_val":"Gamma"},"right":{"type":"Equal","ref_val":"Delta"}}
and therefore i need to deserialze to a trait (dyn Condition) using the type tags in the serialized markup...
I would say the “classic” way to solve this problem is by deserializing into an enum with one variant per potential “real” type you're going to deserialize into. Unfortunately, And is generic, which means those generic parameters have to exist on the enum as well, so you have to specify them where you deserialize.
use serde::{Deserialize, Serialize};
use serde_json; // 1.0.91 // 1.0.152
pub trait Condition {
fn validate(&self, s: &str) -> bool;
}
#[derive(PartialEq, Debug, Serialize, Deserialize)]
pub struct Equal {
ref_val: String,
}
impl Condition for Equal {
fn validate(&self, s: &str) -> bool {
self.ref_val == s
}
}
#[derive(PartialEq, Debug, Serialize, Deserialize)]
pub struct And<A, B>
where
A: Condition + ?Sized,
B: Condition + ?Sized,
{
left: Box<A>,
right: Box<B>,
}
impl<A, B> Condition for And<A, B>
where
A: Condition + ?Sized,
B: Condition + ?Sized,
{
fn validate(&self, s: &str) -> bool {
self.left.validate(s) && self.right.validate(s)
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
enum Expr<A, B>
where
A: Condition + ?Sized,
B: Condition + ?Sized,
{
Equal(Equal),
And(And<A, B>),
}
fn main() {
let c = And {
left: Box::new(Equal {
ref_val: "goofy".to_string(),
}),
right: Box::new(Equal {
ref_val: "goofy".to_string(),
}),
};
let s = serde_json::to_string(&c).unwrap();
let d: Expr<Equal, Equal> = serde_json::from_str(&s).unwrap();
println!("{d:?}");
}
Prints And(And { left: Equal { ref_val: "goofy" }, right: Equal { ref_val: "goofy" } })
I removed the generics from the combinator conditions, so i can now use typetag like #EvilTak suggested:
#[derive(Serialize, Deserialize)]
#[serde(tag="type")]
pub struct And {
left: Box<dyn Condition>,
right: Box<dyn Condition>,
}
#[typetag::serde]
impl Condition for And {
fn validate(&self, s: &str) -> bool { self.left.validate(s) && self.right.validate(s) }
}
(on the downside, i had to remove the derive macros PartialEq, and Debug)
Interesting side fact: i have to keep the #[serde(tag="type")] on the And Struct because otherwise the typetag will be omitted in the serialization (for the primitive consitions it is not needed)
UPDATE: typetag adds the type tag only for trait objects so the #[serde(tag="type")] is not needed...
I'm working on an event-driven particle simulation. The code below defines the struct Event and implements the equality traits that are needed for a binary heap. This code is working but the object event built by the second constructor (new_event) has redundant member variables. I want to have a binary heap which works with two different event structs in order to avoid this redundancy and improve my code efficiency which is crucial for my project.
Can I achieve this using traits?
pub struct Event
{
pub time: f64,
pub event_type: EventType,
pub particle_index1: usize,
pub particle_index2: usize,
pub particle_count1: usize,
pub particle_count2: usize
}
impl Event
{
pub fn new_collision(time: f64, particle_index1: usize, particle_index2: usize,
particle_count1: usize, particle_count2: usize) -> Self
{
Self {
time,
particle_index1,
particle_index2,
event_type: EventType::EventCollision,
particle_count1,
particle_count2
}
}
pub fn new_event(time: f64, event_type: EventType, particle_index1: usize, particle_count1:
usize) -> Self
{
Self {
time,
particle_index1,
particle_index2: 0, //REDUNDANT
event_type,
particle_count1,
particle_count2: 0 //REDUNDANT
}
}
}
impl PartialEq for Event
{
fn eq(&self, other: &Self) -> bool
{
self.time == other.time
}
}
impl Eq for Event{}
impl PartialOrd for Event
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering>
{
other.time.partial_cmp(&self.time) //reverse order
}
}
impl Ord for Event
{
fn cmp(&self, other: &Self) -> Ordering
{
self.partial_cmp(other).unwrap()
}
}
In this case it would be interesting to use an enum and a combination with a custom trait to avoid cumbersome access to inner enum structs:
use core::cmp::Ordering;
type EventType = String;
trait Timed {
fn time(&self) -> f64;
}
pub struct CollisionEvent {
pub time: f64,
pub particle_index1: usize,
pub particle_index2: usize,
pub particle_count1: usize,
pub particle_count2: usize,
}
pub struct GenericEvent {
pub time: f64,
pub event_type: EventType,
pub particle_index1: usize,
pub particle_count1: usize,
}
pub enum Event {
Collision(CollisionEvent),
Generic(GenericEvent),
}
impl Timed for CollisionEvent {
fn time(&self) -> f64 {
self.time
}
}
impl Timed for GenericEvent {
fn time(&self) -> f64 {
self.time
}
}
impl Timed for Event {
fn time(&self) -> f64 {
let event: &dyn Timed = match self {
Event::Collision(event) => event,
Event::Generic(event) => event,
};
event.time()
}
}
impl Event {
pub fn new_collision(
time: f64,
particle_index1: usize,
particle_index2: usize,
particle_count1: usize,
particle_count2: usize,
) -> Self {
Self::Collision(CollisionEvent {
time,
particle_index1,
particle_index2,
particle_count1,
particle_count2,
})
}
pub fn new_event(
time: f64,
event_type: EventType,
particle_index1: usize,
particle_count1: usize,
) -> Self {
Self::Generic(GenericEvent {
time,
particle_index1,
event_type,
particle_count1,
})
}
}
impl PartialEq for Event {
fn eq(&self, other: &Self) -> bool {
self.time() == other.time()
}
}
impl Eq for Event {}
impl PartialOrd for Event {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
other.time().partial_cmp(&self.time()) //reverse order
}
}
impl Ord for Event {
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap()
}
}
Playground
It is possible with enums to have a create a data type which can represent multiple "types".
So you could do something like:
pub struct Event {
pub time: f64,
pub event_type: EventType,
pub particle_index1: usize,
pub particle_count1: usize,
}
pub struct Collison {
pub time: f64,
pub event_type: EventType,
pub particle_index1: usize,
pub particle_index2: usize,
pub particle_count1: usize,
pub particle_count2: usize
}
pub enum EventOrCollision {
Event(Event),
Collision(Collision)
}
So you could now have a BinaryHeap<EventOrCollision>>. However the EventOrCollision would have to implement PartialOrd and the other type constraints on BinaryHeap. You would need to use pattern matching around this type.
However Rust enums are constant width/size (+1 for the discriminant bit) so the binary heap would take up the same space in memory as you have currently. You would avoid initializing the extra usize fields although that is incredible cheap.
I want to serialize a HashMap with structs as keys:
use serde::{Deserialize, Serialize}; // 1.0.68
use std::collections::HashMap;
fn main() {
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
struct Foo {
x: u64,
}
#[derive(Serialize, Deserialize, Debug)]
struct Bar {
x: HashMap<Foo, f64>,
}
let mut p = Bar { x: HashMap::new() };
p.x.insert(Foo { x: 0 }, 0.0);
let serialized = serde_json::to_string(&p).unwrap();
}
This code compiles, but when I run it I get an error:
Error("key must be a string", line: 0, column: 0)'
I changed the code:
#[derive(Serialize, Deserialize, Debug)]
struct Bar {
x: HashMap<u64, f64>,
}
let mut p = Bar { x: HashMap::new() };
p.x.insert(0, 0.0);
let serialized = serde_json::to_string(&p).unwrap();
The key in the HashMap is now a u64 instead of a string. Why does the first code give an error?
You can use serde_as from the serde_with crate to encode the HashMap as a sequence of key-value pairs:
use serde_with::serde_as; // 1.5.1
#[serde_as]
#[derive(Serialize, Deserialize, Debug)]
struct Bar {
#[serde_as(as = "Vec<(_, _)>")]
x: HashMap<Foo, f64>,
}
Which will serialize to (and deserialize from) this:
{
"x":[
[{"x": 0}, 0.0],
[{"x": 1}, 0.0],
[{"x": 2}, 0.0]
]
}
There is likely some overhead from converting the HashMap to Vec, but this can be very convenient.
According to JSONs specification, JSON keys must be strings. serde_json uses fmt::Display in here, for some non-string keys, to allow serialization of wider range of HashMaps. That's why HashMap<u64, f64> works as well as HashMap<String, f64> would. However, not all types are covered (Foo's case here).
That's why we need to provide our own Serialize implementation:
impl Display for Foo {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(f, "{}", self.x)
}
}
impl Serialize for Bar {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(self.x.len()))?;
for (k, v) in &self.x {
map.serialize_entry(&k.to_string(), &v)?;
}
map.end()
}
}
(playground)
I've found the bulletproof solution 😃
Extra dependencies not required
Compatible with HashMap, BTreeMap and other iterable types
Works with flexbuffers
The following code converts a field (map) to the intermediate Vec representation:
pub mod vectorize {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::iter::FromIterator;
pub fn serialize<'a, T, K, V, S>(target: T, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: IntoIterator<Item = (&'a K, &'a V)>,
K: Serialize + 'a,
V: Serialize + 'a,
{
let container: Vec<_> = target.into_iter().collect();
serde::Serialize::serialize(&container, ser)
}
pub fn deserialize<'de, T, K, V, D>(des: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: FromIterator<(K, V)>,
K: Deserialize<'de>,
V: Deserialize<'de>,
{
let container: Vec<_> = serde::Deserialize::deserialize(des)?;
Ok(T::from_iter(container.into_iter()))
}
}
To use it just add the module's name as an attribute:
#[derive(Debug, Serialize, Deserialize)]
struct MyComplexType {
#[serde(with = "vectorize")]
map: HashMap<MyKey, String>,
}
The remained part if you want to check it locally:
use anyhow::Error;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct MyKey {
one: String,
two: u16,
more: Vec<u8>,
}
#[derive(Debug, Serialize, Deserialize)]
struct MyComplexType {
#[serde(with = "vectorize")]
map: HashMap<MyKey, String>,
}
fn main() -> Result<(), Error> {
let key = MyKey {
one: "1".into(),
two: 2,
more: vec![1, 2, 3],
};
let mut map = HashMap::new();
map.insert(key.clone(), "value".into());
let instance = MyComplexType { map };
let serialized = serde_json::to_string(&instance)?;
println!("JSON: {}", serialized);
let deserialized: MyComplexType = serde_json::from_str(&serialized)?;
let expected_value = "value".to_string();
assert_eq!(deserialized.map.get(&key), Some(&expected_value));
Ok(())
}
And on the Rust playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=bf1773b6e501a0ea255ccdf8ce37e74d
While all provided answers will fulfill the goal of serializing your HashMap to json they are ad hoc or hard to maintain.
One correct way to allow a specific data structure to be serialized with serde as keys in a map, is the same way serde handles integer keys in HashMaps (which works): They serialize the value to String. This has a few advantages; namely
Intermediate data-structure omitted,
no need to clone the entire HashMap,
easier maintained by applying OOP concepts, and
serialization usable in more complex structures such as MultiMap.
This can be done by manually implementing Serialize and Deserialize for your data-type.
I use composite ids for maps.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct Proj {
pub value: u64,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct Doc {
pub proj: Proj,
pub value: u32,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct Sec {
pub doc: Doc,
pub value: u32,
}
So now manually implementing serde serialization for them is kind of a hassle, so instead we delegate the implementation to the FromStr and From<Self> for String (Into<String> blanket) traits.
impl From<Doc> for String {
fn from(val: Doc) -> Self {
format!("{}{:08X}", val.proj, val.value)
}
}
impl FromStr for Doc {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match parse_doc(s) {
Ok((_, p)) => Ok(p),
Err(e) => Err(e.to_string()),
}
}
}
In order to parse the Doc we make use of nom. The parse functionality below is explained in their examples.
fn is_hex_digit(c: char) -> bool {
c.is_digit(16)
}
fn from_hex8(input: &str) -> Result<u32, std::num::ParseIntError> {
u32::from_str_radix(input, 16)
}
fn parse_hex8(input: &str) -> IResult<&str, u32> {
map_res(take_while_m_n(8, 8, is_hex_digit), from_hex8)(input)
}
fn parse_doc(input: &str) -> IResult<&str, Doc> {
let (input, proj) = parse_proj(input)?;
let (input, value) = parse_hex8(input)?;
Ok((input, Doc { value, proj }))
}
Now we need to hook up self.to_string() and str::parse(&str) to serde we can do this using a simple macro.
macro_rules! serde_str {
($type:ty) => {
impl Serialize for $type {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let s: String = self.clone().into();
serializer.serialize_str(&s)
}
}
impl<'de> Deserialize<'de> for $type {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
paste! {deserializer.deserialize_string( [<$type Visitor>] {})}
}
}
paste! {struct [<$type Visitor>] {}}
impl<'de> Visitor<'de> for paste! {[<$type Visitor>]} {
type Value = $type;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("\"")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match str::parse(v) {
Ok(id) => Ok(id),
Err(_) => Err(serde::de::Error::custom("invalid format")),
}
}
}
};
}
Here we are using paste to interpolate the names. Beware that now the struct will always serialize as defined above. Never as a struct, always as a string.
It is important to implement fn visit_str instead of fn visit_string because visit_string defers to visit_str.
Finally, we have to call the macro for our custom structs
serde_str!(Sec);
serde_str!(Doc);
serde_str!(Proj);
Now the specified types can be serialized to and from string with serde.
I'm using quickcheck and would like to implement quickcheck::Arbitrary for a struct. This trait has to exist in the same file/crate that the struct is defined, but I don't want it in the release binary.
pub struct c_struct {
pub i64_: i64,
pub u64_: u64,
pub u32_: u32,
}
// #[cfg(test)] does not work
impl quickcheck::Arbitrary for c_struct {
fn arbitrary<G: quickcheck::Gen>(g: &mut G) -> c_struct {
c_struct {
i64_: i64::arbitrary(g),
u64_: u64::arbitrary(g),
u32_: u32::arbitrary(g),
}
}
}
You can use the conditional compilation attribute #[cfg()] here:
pub struct c_struct {
pub i64_: i64,
pub u64_: u64,
pub u32_: u32,
}
#[cfg(test)]
impl quickcheck::Arbitrary for c_struct {
fn arbitrary<G: quickcheck::Gen>(g: &mut G) -> c_struct {
c_struct {
i64_: i64::arbitrary(g),
u64_: u64::arbitrary(g),
u32_: u32::arbitrary(g),
}
}
}
A common solution to this is to use a newtype that is only defined in the tests:
struct c_struct {
pub i64_: i64,
pub u64_: u64,
pub u32_: u32,
}
#[cfg(test)]
mod test {
struct ArbitraryCStruct(c_struct);
impl quickcheck::Arbitrary for ArbitraryCStruct {
fn arbitrary<G: quickcheck::Gen>(g: &mut G) -> ArbitraryCStruct {
ArbitraryCStruct(c_struct {
i64_: i64::arbitrary(g),
u64_: u64::arbitrary(g),
u32_: u32::arbitrary(g),
})
}
}
}
You can then accept this in your quickcheck function. If you need to, you can extract the value using .0 or implement the From or Into trait as needed.
I have the following struct that works fine:
pub struct Pattern {
pub regex: &'static str,
pub view: Fn (Request) -> Response,
}
But I'd like to change view to accept any type that implements Renderable (trait constraint). I was expecting to make it work this way:
pub struct Pattern {
pub regex: &'static str,
pub view: Fn <T: Renderable> (Request) -> T,
}
But no luck. Any ideas?
You want to use a where clause on the struct (and any implementations for that struct):
trait A { fn moo(&self); }
struct S;
struct Pattern<T>
where T: A
{
view: Fn (T) -> S,
}
fn main() {}