简体   繁体   中英

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.

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

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

Context

I use the external library 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.

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).

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. 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 . Here is a screenshot that represents what I would like to implement in this DLL: ! TerminalMessages demonstration

Additionally

I am interested in all the suggestions about Best Practices in Rust and Code Optimization.

I think there are a couple of misconceptions in your code, 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 .
  • You don't need to wrap the objects themselves in a Mutex because the entire HashMap is already in a 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 . This makes the HashMap definition a little cleaner
  • write state_entry helper function to simplify initialization
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!("");
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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