简体   繁体   English

如何以 str 为键、Trait 为值实现一个全局且可变的 HashMap?

[英]How to implement a global and mutable HashMap with str as key and Trait as values?

I am trying to implement value key pairs whose key would be a string and the value would be an object type that implements some predefined functions.我正在尝试实现值键对,其键是字符串,值是实现一些预定义函数的对象类型。 The problem I encounter is that a Trait (which I use in this case as a kind of inheritance) does not have a predefined size and is not threads safely.我遇到的问题是 Trait(在这种情况下我将其用作一种继承)没有预定义的大小并且不是安全的线程。

Code代码

Create Trait and structs创建特征和结构

/// _State line to define the basis of the 'State'
/// type with signatures of specific functions
pub trait _State {
    fn as_string(&self, text: String) -> String;
    fn print(&self);
}

/// Status type to add your custom colors
pub struct RGBState {
    name: String,
    color: (u8, u8, u8),
    character: String,
}

/// Default state type using preconfigured ANSI colors
pub struct State {
    name: String,
    color: Color,
    character: String,
}

Create a global and mutable HashMap with Traits as values创建一个以 Traits 为值的全局且可变的 HashMap

lazy_static! {
    static ref StateOK: Mutex<State> = {
        let mut state = State{
            name: String::from("OK"),
            color: Color::Green,
            character: "+".to_string()
        };
        Mutex::new(state)
    };

    static ref STATES: Mutex<HashMap<&'static str, &'static Lazy<Mutex<dyn _State + 'static>>>> = {
        let mut _states = HashMap::from(
            [
                (
                    "OK",
                    Lazy::new(StateOK)
                )
            ]
        );
        Mutex::new(_states)
    };
}

All source code is available on github (not compilable yiet): https://github.com/mauricelambert/TerminalMessages所有源代码都在 github 上提供(不可编译 yet): https ://github.com/mauricelambert/TerminalMessages

Context语境

I use the external library lazy_static .我使用外部库lazy_static

Assumptions假设

I guess I should use the Mutex, Lazy or other types that would allow me to make a mutable and global value.我想我应该使用 Mutex、Lazy 或其他允许我创建可变和全局值的类型。

Problem问题

I have no idea how I will define the size of the objects that I will define in values and that can be of different types whose common basis are function signatures.我不知道如何定义我将在值中定义的对象的大小,这些对象可以是不同类型,其共同基础是函数签名。

  • I have at least 2 types of objects that implement the Trait and that can be added as a value: a 'simple' type with preconfigured colors and a more complex type or the colors are created by the developer with 3 octects (RGB - Red Green Blue).我至少有 2 种类型的对象实现 Trait 并且可以作为值添加:具有预配置颜色的“简单”类型和更复杂的类型,或者颜色由开发人员使用 3 个八位字节(RGB - 红绿蓝色的)。

Project项目

The overall purpose of my code is to implement a DLL in Rust with interfaces in other languages that would allow to display formatted and colored messages in the console.我的代码的总体目的是在 Rust 中实现一个 DLL,该 DLL 具有其他语言的接口,允许在控制台中显示格式化和彩色消息。 Each element of the message formatting must be "configurable" by the developer (the color, the character that represents the type of message, the progress bar ect...).消息格式的每个元素都必须由开发人员“配置”(颜色、表示消息类型的字符、进度条等......)。 It must be able to use any of these message types by calling a function in which it will have to specify the message type and the content of the message.它必须能够通过调用必须指定消息类型和消息内容的函数来使用这些消息类型中的任何一种。

Example/Demonstration示例/演示

I implemented similar code in Python whose source code is on github: https://github.com/mauricelambert/PythonToolsKit/blob/main/PythonToolsKit/PrintF.py .我在 Python 中实现了类似的代码,其源代码在 github 上: https ://github.com/mauricelambert/PythonToolsKit/blob/main/PythonToolsKit/PrintF.py。 Here is a screenshot that represents what I would like to implement in this DLL: !这是一个屏幕截图,代表我想在这个 DLL 中实现的内容:! TerminalMessages demonstration TerminalMessages 演示

Additionally此外

I am interested in all the suggestions about Best Practices in Rust and Code Optimization.我对所有关于 Rust 和代码优化最佳实践的建议感兴趣。

I think there are a couple of misconceptions in your code, imo:我认为您的代码中存在一些误解,imo:

  • To store trait objects in a HashMap , you need wrap them in a Box , because, as you already realized, trait objects are not Sized .要将特征对象存储在HashMap中,您需要将它们包装在Box中,因为正如您已经意识到的那样,特征对象不是Sized
  • You don't need to wrap the objects themselves in a Mutex because the entire HashMap is already in a Mutex .您不需要将对象本身包装在Mutex中,因为整个HashMap已经在Mutex中。

With that in mind, here is a working implementation :考虑到这一点,这是一个有效的实现

use std::{collections::HashMap, sync::Mutex};

use lazy_static::lazy_static;

/// Preconfigured ANSI colors constants
#[derive(Clone)]
pub enum Color {
    Black,  // 0
    Red,    // 1
    Green,  // 2
    Yellow, // 3
    Blue,   // 4
    Purple, // 5
    Cyan,   // 6
    White,  // 7
}

impl Color {
    fn value(&self) -> i32 {
        match *self {
            Color::Black => 0,
            Color::Red => 1,
            Color::Green => 2,
            Color::Yellow => 3,
            Color::Blue => 4,
            Color::Purple => 5,
            Color::Cyan => 6,
            Color::White => 7,
        }
    }
}

/// _State line to define the basis of the 'State'
/// type with signatures of specific functions
pub trait _State {
    fn as_string(&self, text: String) -> String;
    fn print(&self);
}

/// Default state type using preconfigured ANSI colors
#[derive(Clone)]
pub struct State {
    name: String,
    color: Color,
    character: String,
}

impl _State for State {
    fn as_string(&self, text: String) -> String {
        format!(
            "\x1b[3{color}m[{character}] {text}\x1b[0m",
            color = self.color.value(),
            character = self.character,
            text = text,
        )
    }

    fn print(&self) {
        println!("{}", self.as_string(self.name.clone()));
    }
}

lazy_static! {
    static ref STATE_OK: State = {
        State {
            name: String::from("OK"),
            color: Color::Green,
            character: "+".to_string(),
        }
    };
    static ref STATES: Mutex<HashMap<&'static str, Box<dyn _State + Send>>> = {
        let _states: HashMap<&'static str, Box<dyn _State + Send>> = HashMap::from([
            ("OK", Box::new(STATE_OK.clone()) as Box<dyn _State + Send>),
            (
                "NOK",
                Box::new(State {
                    name: String::from("NOK"),
                    color: Color::Yellow,
                    character: "-".to_string(),
                }) as Box<dyn _State + Send>,
            ),
        ]);
        Mutex::new(_states)
    };
}

fn main() {
    println!("{}", STATES.lock().unwrap().len());
    for (key, value) in &*STATES.lock().unwrap() {
        println!("{}:", key);
        value.print();
        println!("");
    }
}

Further tweaks进一步的调整

Those are more my opinion, take them or leave them.这些更多是我的意见,要么接受,要么离开。

  • Make trait _State depend on Send , as they all have to be Send to be storable in the HashMap .使trait _State依赖于Send ,因为它们都必须是Send才能存储在HashMap中。 This makes the HashMap definition a little cleaner这使得HashMap定义更清晰
  • write state_entry helper function to simplify initialization编写state_entry辅助函数以简化初始化
use std::{collections::HashMap, sync::Mutex};

use lazy_static::lazy_static;

/// Preconfigured ANSI colors constants
#[derive(Clone)]
pub enum Color {
    Black,  // 0
    Red,    // 1
    Green,  // 2
    Yellow, // 3
    Blue,   // 4
    Purple, // 5
    Cyan,   // 6
    White,  // 7
}

impl Color {
    fn value(&self) -> i32 {
        match *self {
            Color::Black => 0,
            Color::Red => 1,
            Color::Green => 2,
            Color::Yellow => 3,
            Color::Blue => 4,
            Color::Purple => 5,
            Color::Cyan => 6,
            Color::White => 7,
        }
    }
}

/// _State line to define the basis of the 'State'
/// type with signatures of specific functions
pub trait _State: Send {
    fn as_string(&self, text: String) -> String;
    fn print(&self);
}

/// Default state type using preconfigured ANSI colors
#[derive(Clone)]
pub struct State {
    name: String,
    color: Color,
    character: String,
}

impl _State for State {
    fn as_string(&self, text: String) -> String {
        format!(
            "\x1b[3{color}m[{character}] {text}\x1b[0m",
            color = self.color.value(),
            character = self.character,
            text = text,
        )
    }

    fn print(&self) {
        println!("{}", self.as_string(self.name.clone()));
    }
}

fn state_entry(
    name: &'static str,
    entry: impl _State + 'static,
) -> (&'static str, Box<dyn _State>) {
    (name, Box::new(entry))
}

lazy_static! {
    static ref STATE_OK: State = {
        State {
            name: String::from("OK"),
            color: Color::Green,
            character: "+".to_string(),
        }
    };
    static ref STATES: Mutex<HashMap<&'static str, Box<dyn _State>>> = {
        Mutex::new(HashMap::from([
            state_entry("OK", STATE_OK.clone()),
            state_entry(
                "NOK",
                State {
                    name: String::from("NOK"),
                    color: Color::Yellow,
                    character: "-".to_string(),
                },
            ),
            state_entry(
                "ERROR",
                State {
                    name: String::from("ERROR"),
                    color: Color::Red,
                    character: "!".to_string(),
                },
            ),
        ]))
    };
}

fn main() {
    println!("{} entries\n", STATES.lock().unwrap().len());

    for (key, value) in &*STATES.lock().unwrap() {
        println!("{}:", key);
        value.print();
        println!("");
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM