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

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

我正在尝试实现值键对,其键是字符串,值是实现某些预定义函数的对象类型。我遇到的问题是特征(我在这种情况下将其用作一种继承)没有预定义的大小并且不是线程安全的。

代码

创建特征和结构

/// _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,
}

创建一个以 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)
    };
}

所有源代码都可以在 github 上找到(yiet 不可编译):https://github.com/mauricelambert/TerminalMessages

上下文

我用的是外部库lazy_static.

假设

我想我应该使用 Mutex、Lazy 或其他允许我创建可变全局值的类型。

问题

我不知道如何定义我将在值中定义的对象的大小,这些对象可以是不同类型,其共同基础是函数签名。

项目

我的代码的总体目的是用 Rust 实现一个 DLL,它具有其他语言的接口,允许在控制台中显示格式化和彩色消息。消息格式的每个元素都必须由开发人员“配置”(颜色、表示消息类型的字符、进度条等...)。它必须能够通过调用必须指定消息类型和消息内容的函数来使用这些消息类型中的任何一种。

Example/Demonstration

我在 Python 中实现了类似的代码,其源代码在 github: https://github.com/mauricelambert/PythonToolsKit/blob/main/PythonToolsKit/PrintF.py。 这是代表我想在此 DLL 中实现的内容的屏幕截图:!TerminalMessages demonstration

另外

我对所有关于 Rust 最佳实践和代码优化的建议感兴趣。

我认为您的代码中存在一些误解,imo:

  • 要将特征对象存储在 HashMap 中,您需要将它们包装在 Box 中,因为正如您已经意识到的那样,特征对象不是 Sized.
  • 您不需要将对象本身包装在 Mutex 中,因为整个 HashMap 已经在 Mutex.

考虑到这一点,这里有一个 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!("");
    }
}

进一步调整

这些都是我的意见,要么接受要么放弃。

  • 使 trait _State 依赖于 Send,因为它们都必须是 Send 才能存储在 HashMap 中。这使得 HashMap 定义更清晰一些
  • 编写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!("");
    }
}