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.
/// _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,
}
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
I use the external library lazy_static.
I guess I should use the Mutex, Lazy or other types that would allow me to make a mutable and global value.
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.
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.
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
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:
HashMap
, you need wrap them in a Box
, because, as you already realized, trait objects are not Sized
.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!("");
}
}
Those are more my opinion, take them or leave them.
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 cleanerstate_entry
helper function to simplify initializationuse 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!("");
}
}