Что такое эквивалент Rust хэш-карты TypeScript с необязательными типизированными значениями для определенных ключей? - PullRequest
0 голосов
/ 29 апреля 2018

Я хочу иметь возможность иметь структуру данных, которая может иметь ключи определенных типов (например, структуру Rust), но может иметь дополнительные значения, которые не должны существовать (например, HashMap). Есть ли тип данных, который находится на полпути между ними? Какой лучший способ подойти к этому?

Я изучаю Rust, но ежедневно использую TypeScript. Я знаю, что системы двух типов различны, я просто пытаюсь достичь чего-то похожего. В TypeScript я могу сделать:

interface Example {
  value1: string;
  optional?: number;
  value3?: "hello" | "goodbye";
}

и при создании объекта для сопоставления:

const obj: Example = {
  value1: "this works"
}

Я собираюсь внести свой вклад в DenisKolodin / yew , и первое, что я хотел попробовать - это реализация в произвольном стиле в идиоматическом стиле Rust. Возможность объявлять стили в структуре с определенными типами перечислений («flex», «block» и т. Д.), Но не требовать каждого возможного выбора стиля / CSS, является ключом к этой библиотеке.

Любые и все примеры приветствуются.

Ответы [ 2 ]

0 голосов
/ 29 апреля 2018

Это не сценарий использования HashMaps в Rust. HashMap имеет ключи, которые неизвестны во время компиляции, а это не то, что вам нужно. Структуры имеют фиксированный набор известных ключей, а перечисления имеют фиксированный набор возможностей.

Прямой перевод вашего кода создает структуру и перечисление, используя Option для обозначения необязательных полей:

#[derive(Debug)]
struct Example {
    value1: String,
    optional: Option<i32>,
    value3: Option<Value3Values>,
}

#[derive(Debug)]
enum Value3Values {
    Hello,
    Goodbye,
}

Хотя исправно, это может раздражать , чтобы построить по сравнению с тем, к чему вы привыкли:

Example {
    value1: String::from("hello"),
    optional: None,
    value3: Some(Value3Values::Goodbye),
}

Мы можем применить некоторые шаги, чтобы улучшить его. Использование Default плюс синтаксис буквального обновления struct упрощает все значения по умолчанию:

#[derive(Debug, Default)]
struct Example { /* ... */ }
Example {
    value1: String::from("hello"),
    value3: Some(Value3Values::Goodbye),
    ..Example::default()
}

Вы также можете применить черты Into / From, чтобы удалить некоторые преобразования:

Example {
    value1: "hello".into(),
    value3: Value3Values::Goodbye.into(),
    ..Example::default()
}

И вы можете заключить его в макрос, чтобы избежать повторения:

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,
    });
}

Существуют даже способы использования строковых литералов для value3, но я бы этого избегал. «Строго типизированные» API раздражают.

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"),
    });
}

Могут даже быть хитрые трюки с макросами, чтобы избежать необходимости вызывать макрос value3! внутри thing!.


A очень продвинутый метод будет использовать скрипт сборки для генерации пользовательских макросов для каждого набора атрибутов CSS, возможно, с использованием MDN CSS JSON DB . Вы бы получили что-то вроде:

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,
    };
}
0 голосов
/ 29 апреля 2018

В TypeScript и JavaScript есть два обнуляемых типов : null и undefined.

Большинство других языков изначально этого не делали, потому что два варианта опций явно не выигрывают (ср. https://www.youtube.com/watch?v=PSGEjv3Tqo0&feature=youtu.be&t=9m21s из https://basarat.gitbooks.io/typescript/docs/javascript/null-undefined.html; https://github.com/Microsoft/TypeScript/wiki/Coding-guidelines#null-and-undefined).

В Rust вы бы вместо этого использовали Option, что (в большинстве случаев) соответствует как null, так и undefined JavaScript и TypeScript.

struct Example {
    value1: String,
    optional: Option<i64>,
    value3: Option<String>
}

P.S. Чтобы указать только те поля, которые вы хотите указать, и оставить все остальные поля на None, вы можете указать языку для реализации Default для структуры:

#[derive(Default)]
struct Example {
    value1: String,
    optional: Option<i64>,
    value3: Option<String>
}

fn new_example() -> Example {
    Example {
        value1: "foobar".into(),
        ..Default::default()
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...