I want to be able to have a data structure that can have keys with specific types (like a Rust struct) but may have optional values that don't need to exist (like a HashMap
). Is there a data type that is halfway between both? What's the best way to approach this?
I'm learning Rust, but use TypeScript on a daily basis. I know that the two type systems are different, I'm just trying to achieve something similar. In TypeScript, I can do:
interface Example {
value1: string;
optional?: number;
value3?: "hello" | "goodbye";
}
and when creating an object to match it:
const obj: Example = {
value1: "this works"
}
I'm exploring contributing to DenisKolodin/yew and the first thing I wanted to try my hand at was a free-style implementation in an idiomatic Rust way. Being able to declare styles in a struct with specific enum types ("flex", "block" etc) but not require each possible style/CSS selector is key to this kind of library.
Any and all examples are welcome.
That's not the usecase for HashMaps
in Rust. A HashMap
has keys that are unknown at compile time, which is not what you want here. Structs have a fixed set of known keys, while enums have a fixed set of possibilities.
The direct translation of your code creates a struct and an enum, using Option
to denote the optional fields:
#[derive(Debug)]
struct Example {
value1: String,
optional: Option<i32>,
value3: Option<Value3Values>,
}
#[derive(Debug)]
enum Value3Values {
Hello,
Goodbye,
}
While serviceable, this can be annoying to construct compared to what you are used to:
Example {
value1: String::from("hello"),
optional: None,
value3: Some(Value3Values::Goodbye),
}
We can apply some steps to improve it. Using Default
plus the struct literal update syntax makes all the default values easier:
#[derive(Debug, Default)]
struct Example { /* ... */ }
Example {
value1: String::from("hello"),
value3: Some(Value3Values::Goodbye),
..Example::default()
}
You can then also apply the Into
/ From
traits to remove some of the conversion:
Example {
value1: "hello".into(),
value3: Value3Values::Goodbye.into(),
..Example::default()
}
And you can wrap it up in a macro to avoid the repetition:
macro_rules! thing {
($t:ident, { $( $name:ident : $val:expr ),*, }) => (
$t {
$( $name: Into::into($val) ),*,
.. $t::default()
}
);
}
fn main() {
thing!(Example, {
value1: "hello",
});
thing!(Example, {
value1: "hello",
optional: 32,
});
thing!(Example, {
value1: "hello",
value3: Value3Values::Hello,
});
thing!(Example, {
value1: "hello",
optional: 32,
value3: Value3Values::Hello,
});
}
There are even ways to use string literals for value3
, but I'd avoid it. "Stringly-typed" APIs are annoying.
macro_rules! value3 {
("hello") => (Value3Values::Hello);
("goodbye") => (Value3Values::Goodbye);
}
fn main() {
thing!(Example, {
value1: "hello",
value3: value3!("hello"),
});
thing!(Example, {
value1: "hello",
optional: 32,
value3: value3!("goodbye"),
});
}
There may even be cleverer macro tricks to avoid needing to call the macro value3!
inside thing!
.
A very advanced technique would be to use a build script to generate custom macros for every set of CSS attributes, perhaps using the MDN CSS JSON DB. You'd end up with something like:
macro_rules! example {
( $( $name:ident : $val:tt ),*, ) => (
Example {
$( $name: example!(field @ $name : $val) ),*,
.. Example::default()
}
);
// Internal details
( field @ value1 : $val:expr ) => (Into::into($val));
( field @ optional : $val:expr ) => (Into::into($val));
( field @ value3 : hello ) => (Some(Value3Values::Hello));
( field @ value3 : goodbye ) => (Some(Value3Values::Goodbye));
( field @ value3 : $val:expr ) => (
compile_error!(r#"value3 can only be "hello" or "goodbye""#)
);
}
fn main() {
example! {
value1: "name",
};
example! {
optional: 42,
};
example! {
value1: "name",
value3: hello,
};
example! {
value1: "name",
optional: 42,
value3: goodbye,
};
}
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments