What is the Rust equivalent of a TypeScript hashmap with optional typed values for specific keys?

Alexander Mattoni

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.

Shepmaster

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.

edited at
0

Comments

0 comments
Login to comment

Related

What is the TypeScript equivalent of a Rust struct?

How to print keys of specific values in HashMap?

Filtering keys in hashmap in rust

TypeScript type with specific set of keys and values

What is the Rust equivalent of TypeScript's Array.map with destructuring?

Get typed object keys in Typescript

Keys of a nested "typed" object in typescript

How to get a specific duplicate values keys using hashMap in java

Aggregating values in hashmap by keys

Is it possible to have mixed specific typed keys in a TypeScript type, and a generic key type?

Create dynamic Pydantic model with typed optional values

What is the TypeScript equivalent of "PropTypes.oneOf" (restrict a variable to subset of values)

Get the keys of an already typed object as types in typescript

Typescript - How to get keys behind specific values from an object

What is the Rust equivalent to append in Go?

Is there a Rust equivalent of what bigInt is to python?

What is the Rust equivalent for abstract classes?

Why are custom objects not equivalent keys for a HashMap?

creating a string joining the keys of a hashmap in rust

Convert HashMap into String with keys and values

Reverse HashMap keys and values in Java

Why are these values retrieved with keys in HashMap?

Does Blazor have an enum-typed equivalent of WinForms' Keys for KeyboardEventArgs?

In typescript, in a typed function argument, optional properties are ignored by intellisense. Why?

Array reduce and optional values in typescript

TypeScript: Discriminated Unions with optional values

Sequence.compactMap equivalent for tuples with optional values

Equivalent of specific template usage in C++ for Rust

What's the Equivalent of str_replace(array_keys([]), array_values([]), $subject); in Laravel Collection