Wanting to store a large number of byte arrays and aim to do this with a AHashMap<Vec<u8>> and using Cow borrow from it and only write when needed, this should also cause it to be deduplicated. However my attempts so far have been futile:
#![feature(hash_set_entry)]
use std::borrow::{Borrow, Cow, ToOwned};
use std::hash::Hash;
use ahash::AHashSet;
#[derive(Debug)]
struct CipherText<'a> {
ciphertext: Cow<'a, Vec<u8>>,
ciphers: Vec<Cipher<'a>>,
}
#[derive(Debug)]
struct Cipher<'a> {
cipher_id: Cow<'a, Vec<u8>>,
keys: Vec<Cow<'a, Vec<u8>>>,
}
fn main() {
let mut string_table: AHashSet<Vec<u8>> = vec![
"Hello World!".as_bytes().to_vec(),
"atbash".as_bytes().to_vec(),
"caesar_decrypt".as_bytes().to_vec(),
"5".as_bytes().to_vec(),
]
.into_iter()
.collect();
let mut ciphertexts: Vec<CipherText> = vec![
CipherText {
ciphertext: Cow::Borrowed(
string_table
.get(&"Hello World!".as_bytes().to_vec())
.unwrap(),
),
ciphers: vec![Cipher {
cipher_id: Cow::Borrowed(string_table.get(&"atbash".as_bytes().to_vec()).unwrap()),
keys: vec![],
}],
},
CipherText {
ciphertext: Cow::Borrowed(
string_table
.get(&"Hello World!".as_bytes().to_vec())
.unwrap(),
),
ciphers: vec![Cipher {
cipher_id: Cow::Borrowed(
string_table
.get(&"caesar_decrypt".as_bytes().to_vec())
.unwrap(),
),
keys: vec![Cow::Borrowed(
string_table.get(&"5".as_bytes().to_vec()).unwrap(),
)],
}],
},
];
string_table.insert("TEST".as_bytes().to_vec());
string_table.insert("TEST2".as_bytes().to_vec());
ciphertexts[0].ciphertext = Cow::Borrowed(
&string_table.get_or_insert_owned(&"Goodbye Cruel World...".as_bytes().to_vec()),
);
}
Both of the TEST lines as well as the ciphertext[0] line error as follows
error[E0502]: cannot borrow `string_table` as mutable because it is also borrowed as immutable
--> src/main.rs:61:5
|
33 | string_table
| ------------ immutable borrow occurs here
...
61 | string_table.insert("TEST".as_bytes().to_vec());
| ^^^^^^^^^^^^ mutable borrow occurs here
...
64 | ciphertexts[0].ciphertext = Cow::Borrowed(
| ----------- immutable borrow later used here
My aim is for all the byte arrays to only be references and then clone, add to the string_table and reference that if I change it. This data will be stored in a custom binary format and this is the start of the process for writing the serialiser and deserialiser. Hope this all makes sense!
You cannot mutate string_table while a CipherText holds a reference to it. This is simply one of Rust's core invariants. So you cannot use Cow. The typical ways around it are generally either:
Use indexes/keys to reference the other structure, which unfortunately I can't see a good way to do that in this scenario.
Use Rcs so that the ciphertexts and the table share ownership of the bytes. This does mean that the ciphertexts can stand on their own, but you can always use the table as a broker to dedup elements before using them with judicious use of get_or_insert.
#![feature(hash_set_entry)]
use std::rc::Rc;
use ahash::AHashSet;
#[derive(Debug)]
struct CipherText {
ciphertext: Rc<Vec<u8>>,
ciphers: Vec<Cipher>,
}
#[derive(Debug)]
struct Cipher {
cipher_id: Rc<Vec<u8>>,
keys: Vec<Rc<Vec<u8>>>,
}
fn main() {
let mut string_table: AHashSet<Rc<Vec<u8>>> = vec![
Rc::new("Hello World!".as_bytes().to_vec()),
Rc::new("atbash".as_bytes().to_vec()),
Rc::new("caesar_decrypt".as_bytes().to_vec()),
Rc::new("5".as_bytes().to_vec()),
]
.into_iter()
.collect();
let mut ciphertexts: Vec<CipherText> = vec![
CipherText {
ciphertext: string_table
.get_or_insert(Rc::new("Hello World!".as_bytes().to_vec()))
.clone(),
ciphers: vec![Cipher {
cipher_id: string_table
.get_or_insert(Rc::new("atbash".as_bytes().to_vec()))
.clone(),
keys: vec![],
}],
},
CipherText {
ciphertext: string_table
.get_or_insert(Rc::new("Hello World!".as_bytes().to_vec()))
.clone(),
ciphers: vec![Cipher {
cipher_id: string_table
.get_or_insert(Rc::new("caesar_decrypt".as_bytes().to_vec()))
.clone(),
keys: vec![string_table
.get_or_insert(Rc::new("5".as_bytes().to_vec()))
.clone()],
}],
},
];
string_table.insert(Rc::new("TEST".as_bytes().to_vec()));
string_table.insert(Rc::new("TEST2".as_bytes().to_vec()));
ciphertexts[0].ciphertext = string_table
.get_or_insert(Rc::new("Goodbye Cruel World...".as_bytes().to_vec()))
.clone();
}
This will have the same effect as Cow, the elements owned by the Rcs are immutable so a new element must be made to make changes.
Related
I am creating bindings in Rust for a C library and Bindgen generated enums like:
// Rust
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum rmw_qos_history_policy_t {
RMW_QOS_POLICY_HISTORY_SYSTEM_DEFAULT = 0,
RMW_QOS_POLICY_HISTORY_KEEP_LAST = 1,
RMW_QOS_POLICY_HISTORY_KEEP_ALL = 2,
RMW_QOS_POLICY_HISTORY_UNKNOWN = 3,
}
I need to convert these to:
// Rust
pub enum QoSHistoryPolicy {
SystemDefault = 0,
KeepLast = 1,
KeepAll = 2,
Unknown = 3,
}
When importing constant values from this C library:
// C library
const rmw_qos_history_policy_t some_value_from_C = RMW_QOS_POLICY_HISTORY_SYSTEM_DEFAULT;
I would like to do something like:
let some_value: QoSHistoryPolicy = some_value_from_C;
How can I go about it?
The compiler does not inspect enums for ABI compatibility, and as such does not provide a direct way to convert values between these types. A few possible solutions follow.
1. One-by-one matching
This is trivial and safe, albeit leading to exhaustive code.
impl From<rmw_qos_history_policy_t> for QoSHistoryPolicy {
fn from(x: rmw_qos_history_policy_t) -> Self {
use rmw_qos_history_policy_t::*;
match x {
RMW_QOS_POLICY_HISTORY_SYSTEM_DEFAULT => QoSHistoryPolicy::SystemDefault,
RMW_QOS_POLICY_HISTORY_KEEP_LAST => QoSHistoryPolicy::KeepLast,
RMW_QOS_POLICY_HISTORY_KEEP_ALL => QoSHistoryPolicy::KeepAll,
RMW_QOS_POLICY_HISTORY_UNKNOWN => QoSHistoryPolicy::Unknown,
}
}
}
2. Casting + FromPrimitive
Rust allows you to convert field-less enums into an integer type using the as operator. The opposite conversion is not always safe however. Derive FromPrimitive using the num crate to obtain the missing piece.
#[derive(FromPrimitive)]
pub enum QoSHistoryPolicy { ... }
impl From<rmw_qos_history_policy_t> for QoSHistoryPolicy {
fn from(x: rmw_qos_history_policy_t) -> Self {
FromPrimitive::from_u32(x as _).expect("1:1 enum variant matching, all good")
}
}
3. Need an enum?
In the event that you just want an abstraction to low-level bindings, you might go without a new enum type.
#[repr(transparent)]
pub struct QoSHistoryPolicy(rmw_qos_history_policy_t);
The type above contains the same information and binary representation, but can expose an encapsulated API. The conversion from the low-level type to the high-level type becomes trivial. The main downside is that you lose pattern matching over its variants.
4. You're on your own
When absolutely sure that the two enums are equivalent in their binary representation, you can transmute between them. The compiler won't help you here, this is far from recommended.
unsafe {
let policy: QoSHistoryPolicy = std::mem::transmute(val);
}
See also:
How do I match enum values with an integer?
It looks to be a good candidate for the From trait on QoSHistoryPolicy.
impl From<rmw_qos_history_policy_t> for QoSHistoryPolicy {
fn from(raw: rmw_qos_history_policy_t) -> Self {
match raw {
rmw_qos_history_policy_t::RMW_QOS_POLICY_HISTORY_SYSTEM_DEFAULT => QoSHistoryPolicy::SystemDefault,
rmw_qos_history_policy_t::RMW_QOS_POLICY_HISTORY_KEEP_LAST => QoSHistoryPolicy::KeepLast,
rmw_qos_history_policy_t::RMW_QOS_POLICY_HISTORY_KEEP_ALL => QoSHistoryPolicy::KeepAll,
rmw_qos_history_policy_t::RMW_QOS_POLICY_HISTORY_UNKNOWN => QoSHistoryPolicy::Unknown
}
}
}
so this should now work
let some_value: QoSHistoryPolicy = some_value_from_C.into();
I'm attempting to create a game in web assembly. I chose to prepare it in rust and compile it using cargo-web. I managed to get a working game loop, but I have a problem with adding MouseDownEvent listener due to rust borrowing mechanisms. I would very much prefer to write "safe" code (without using "unsafe" keyword)
At this moment the game simply moves a red box from (0,0) to (700,500) with speed depending on the distance. I would like to have the next step to use user click update the destination.
This is the simplified and working code of the game.
static/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>The Game!</title>
</head>
<body>
<canvas id="canvas" width="600" height="600">
<script src="game.js"></script>
</body>
</html>
src/main.rs
mod game;
use game::Game;
use stdweb::console;
use stdweb::traits::*;
use stdweb::unstable::TryInto;
use stdweb::web::document;
use stdweb::web::CanvasRenderingContext2d;
use stdweb::web::html_element::CanvasElement;
use stdweb::web::event::MouseDownEvent;
fn main()
{
let canvas: CanvasElement = document()
.query_selector("#canvas")
.unwrap()
.unwrap()
.try_into()
.unwrap();
canvas.set_width(800u32);
canvas.set_height(600u32);
let context = canvas.get_context().unwrap();
let game: Game = Game::new();
// canvas.add_event_listener(|event: MouseDownEvent|
// {
// game.destination.x = (event.client_x() as f64);
// game.destination.y = (event.client_y() as f64);
// });
game_loop(game, context, 0f64);
}
fn game_loop(mut game : Game, context : CanvasRenderingContext2d, timestamp : f64)
{
game.cycle(timestamp);
draw(&game,&context);
stdweb::web::window().request_animation_frame( |time : f64| { game_loop(game, context, time); } );
}
fn draw(game : &Game, context: &CanvasRenderingContext2d)
{
context.clear_rect(0f64,0f64,800f64,800f64);
context.set_fill_style_color("red");
context.fill_rect(game.location.x, game.location.y, 5f64, 5f64);
}
src/game.rs
pub struct Point
{
pub x: f64,
pub y: f64,
}
pub struct Game
{
pub time: f64,
pub location: Point,
pub destination: Point,
}
impl Game
{
pub fn new() -> Game
{
let game = Game
{
time: 0f64,
location: Point{x: 0f64, y: 0f64},
destination: Point{x: 700f64, y: 500f64},
};
return game;
}
pub fn cycle(&mut self, timestamp : f64)
{
if timestamp - self.time > 10f64
{
self.location.x += (self.destination.x - self.location.x) / 10f64;
self.location.y += (self.destination.y - self.location.y) / 10f64;
self.time = timestamp;
}
}
}
The commented out part of main.rs is my attempt of adding a MouseDownEvent listener. Unfortunately it generates a compilation error:
error[E0505]: cannot move out of `game` because it is borrowed
--> src\main.rs:37:15
|
31 | canvas.add_event_listener(|event: MouseDownEvent|
| - ----------------------- borrow of `game` occurs here
| _____|
| |
32 | | {
33 | | game.destination.x = (event.client_x() as f64);
| | ---- borrow occurs due to use in closure
34 | | game.destination.y = (event.client_y() as f64);
35 | | });
| |______- argument requires that `game` is borrowed for `'static`
36 |
37 | game_loop(game, context, 0f64);
| ^^^^ move out of `game` occurs here
I would very much like to know how to properly implement a way of reading user input into a game. It doesn't need to be asynchronous.
I think that the compiler error message is pretty clear in this case. You're trying to borrow the game in the closure for the 'static lifetime and then you're also trying to move the game. It isn't allowed. I'd recommend to read The Rust Programming Language book again. Focus on chapter 4 - Understanding Ownership.
To make it shorter, your question boils down to something like - how to share a state, which can be mutated. There're plenty of ways how to achieve this goal, but it really depends on your needs (single or multi thread, etc.). I'm going to use Rc & RefCell for this problem.
Rc (std::rc):
The type Rc<T> provides shared ownership of a value of type T, allocated in the heap. Invoking clone on Rc produces a new pointer to the same value in the heap. When the last Rc pointer to a given value is destroyed, the pointed-to value is also destroyed.
RefCell (std::cell):
Values of the Cell<T> and RefCell<T> types may be mutated through shared references (i.e. the common &T type), whereas most Rust types can only be mutated through unique (&mut T) references. We say that Cell<T> and RefCell<T> provide 'interior mutability', in contrast with typical Rust types that exhibit 'inherited mutability'.
Here's what I did to your structures:
struct Inner {
time: f64,
location: Point,
destination: Point,
}
#[derive(Clone)]
pub struct Game {
inner: Rc<RefCell<Inner>>,
}
What does this mean? Inner holds the game state (same fields as the old Game). New Game has just one field inner, which contains the shared state.
Rc<T> (T is RefCell<Inner> in this case) - allows me to clone inner multiple times, but it won't clone the T
RefCell<T> (T is Inner in this case) - allows me to borrow T immutably or mutably, checking is done in the runtime
I can clone the Game structure multiple times now and it won't clone the RefCell<Inner>, just the Game & Rc. Which is what the enclose! macro is doing in the updated main.rs:
let game: Game = Game::default();
canvas.add_event_listener(enclose!( (game) move |event: MouseDownEvent| {
game.set_destination(event);
}));
game_loop(game, context, 0.);
Without the enclose! macro:
let game: Game = Game::default();
// game_for_mouse_down_event_closure holds the reference to the
// same `RefCell<Inner>` as the initial `game`
let game_for_mouse_down_event_closure = game.clone();
canvas.add_event_listener(move |event: MouseDownEvent| {
game_for_mouse_down_event_closure.set_destination(event);
});
game_loop(game, context, 0.);
Updated game.rs:
use std::{cell::RefCell, rc::Rc};
use stdweb::traits::IMouseEvent;
use stdweb::web::event::MouseDownEvent;
#[derive(Clone, Copy)]
pub struct Point {
pub x: f64,
pub y: f64,
}
impl From<MouseDownEvent> for Point {
fn from(e: MouseDownEvent) -> Self {
Self {
x: e.client_x() as f64,
y: e.client_y() as f64,
}
}
}
struct Inner {
time: f64,
location: Point,
destination: Point,
}
impl Default for Inner {
fn default() -> Self {
Inner {
time: 0.,
location: Point { x: 0., y: 0. },
destination: Point { x: 700., y: 500. },
}
}
}
#[derive(Clone)]
pub struct Game {
inner: Rc<RefCell<Inner>>,
}
impl Default for Game {
fn default() -> Self {
Game {
inner: Rc::new(RefCell::new(Inner::default())),
}
}
}
impl Game {
pub fn update(&self, timestamp: f64) {
let mut inner = self.inner.borrow_mut();
if timestamp - inner.time > 10f64 {
inner.location.x += (inner.destination.x - inner.location.x) / 10f64;
inner.location.y += (inner.destination.y - inner.location.y) / 10f64;
inner.time = timestamp;
}
}
pub fn set_destination<T: Into<Point>>(&self, location: T) {
let mut inner = self.inner.borrow_mut();
inner.destination = location.into();
}
pub fn location(&self) -> Point {
self.inner.borrow().location
}
}
Updated main.rs:
use stdweb::traits::*;
use stdweb::unstable::TryInto;
use stdweb::web::document;
use stdweb::web::event::MouseDownEvent;
use stdweb::web::html_element::CanvasElement;
use stdweb::web::CanvasRenderingContext2d;
use game::Game;
mod game;
// https://github.com/koute/stdweb/blob/master/examples/todomvc/src/main.rs#L31-L39
macro_rules! enclose {
( ($( $x:ident ),*) $y:expr ) => {
{
$(let $x = $x.clone();)*
$y
}
};
}
fn game_loop(game: Game, context: CanvasRenderingContext2d, timestamp: f64) {
game.update(timestamp);
draw(&game, &context);
stdweb::web::window().request_animation_frame(|time: f64| {
game_loop(game, context, time);
});
}
fn draw(game: &Game, context: &CanvasRenderingContext2d) {
context.clear_rect(0., 0., 800., 800.);
context.set_fill_style_color("red");
let location = game.location();
context.fill_rect(location.x, location.y, 5., 5.);
}
fn main() {
let canvas: CanvasElement = document()
.query_selector("#canvas")
.unwrap()
.unwrap()
.try_into()
.unwrap();
canvas.set_width(800);
canvas.set_height(600);
let context = canvas.get_context().unwrap();
let game: Game = Game::default();
canvas.add_event_listener(enclose!( (game) move |event: MouseDownEvent| {
game.set_destination(event);
}));
game_loop(game, context, 0.);
}
P.S. Please, before sharing any code in the future, install and use the rustfmt.
In your example game_loop owns game, as it is moved into the loop. So anything that should change game needs to happen inside game_loop. To fit event handling into this, you have multiple options:
Option 1
Let the game_loop poll for events.
You create a queue of events and your game_loop will have some logic to get the first event and handle it.
You will have to deal with synchronization here, so I suggest that you read up on Mutex and Concurrency in general. But it should be a fairly easy task once you get the hang of it. Your loop gets one reference and each event handler gets one, all try to unlock the mutex and then access the queue (vector probably).
This will make your game_loop the monolithic one truth of them all, which is a popular engine design because it is easy to reason about and start with.
But maybe you want to be less centralized.
Option 2
Let events happen outside the loop
This idea would be a bigger refactor. You would put your Game in a lazy_static with a Mutex around it.
Every invocation of the game_loop it will try to get the lock on said Mutex and then perform game calculations.
When an input event happens, that event also tries to get the Mutex on the Game. This means while the game_loop is processing, no input events are handled, but they will try to get in between ticks.
A challenge here would be to preserve input order and to make sure that inputs are processed quick enough. This might be a bigger challenge to get completely right. But the design will give you some possibilities.
A fleshed out version of this idea is Amethyst, which is massively parallel and makes for a clean design. But they employ a quite more complex design behind their engine.
I'm trying to make a macro to define some enum variants that represent assembler instructions.
Right now, I have the following which works fine. This is pretty useless, but eventually I'm going to add some automatically derived methods so it'll have a purpose later on:
macro_rules! define_instructions {
($($inames:tt),+ $(,)*) => {
#[derive(Debug)]
pub enum Instruction {
$($inames),*
}
};
}
define_instructions! {
PsqLux,
PsqLx,
Twi,
}
fn main() {}
The problem is that most (but not all) of the variants I'll be adding will need to have associated values:
define_instructions! {
PsqLux(u32, u32, u32, u32, u32),
PsqLx(u32, u32, u32, u32, u32),
Twi(u32, u32, u32),
SomethingWithoutAVariant,
}
I tried using this to allow for the variants to have associated values (it doesn't actually support associated values yet, it's just a start):
macro_rules! define_instructions {
(#define) => {};
(#define $iname:ident) => {
$iname,
};
(#define $iname:ident $($inames:tt)*) => {
$iname,
define_instructions! { #define $($inames)* }
};
($($inames:tt),+ $(,)*) => {
#[derive(Debug)]
pub enum Instruction {
define_instructions! { #define $($inames)* }
}
};
}
define_instructions! {
PsqLux,
PsqLx,
Twi,
}
That code gave me this error:
error: expected one of `(`, `,`, `=`, `{`, or `}`, found `!`
--> src/main.rs:17:32
|
17 | define_instructions! { #define $($inames)* }
| ^ expected one of `(`, `,`, `=`, `{`, or `}` here
...
22 | / define_instructions! {
23 | | PsqLux,
24 | | PsqLx,
25 | | Twi,
26 | | }
| |_- in this macro invocation
It's seems like I can't use macros inside of an enum definition. Assuming that's true, how could I work around this and be able to generate enum variants that may or may not contain associated values?
Note: I'm sure there's something wrong with the rules I added, but I'd at least like to be able to get to the point where I can call them, so I can get them working.
Edit
mcarton explained another way to do what I'm trying to do, and that's helpful at the moment.
However I'd still like to know how to use a macro inside a enum definition, because in the future I'd be able to like to write macros like this:
define_instructions! {
/// Name: Trap Word Immediate
/// Simplified Mnemonics:
/// twgti rA, value = twi 8, _rA, _value
/// twllei rA, value = twi 6, _rA, _value
twi TO, rA, SIMM;
// and so on...
}
// That macro would become
pub enum Instruction {
/// Name: Trap Word Immediate
/// Usage: twi TO, rA, SIMM
/// Simplified Mnemonics:
/// twi 8, rA, value = twgti rA, value
/// twllei rA, value = twi 6, rA, value
Twi(u32, u32, u32, u32, u32),
}
So I have the workaround, but I'm still curious if it's possible to use macros inside of an enum definition. Is that possible, and if not why isn't it?
C++ example:
for (long i = 0; i < 101; i++) {
//...
}
In Rust I tried:
for i: i64 in 1..100 {
// ...
}
I could easily just declare a let i: i64 = var before the for loop
but I'd rather learn the correct way to doing this, but this resulted in
error: expected one of `#` or `in`, found `:`
--> src/main.rs:2:10
|
2 | for i: i64 in 1..100 {
| ^ expected one of `#` or `in` here
You can use an integer suffix on one of the literals you've used in the range. Type inference will do the rest:
for i in 1i64..101 {
println!("{}", i);
}
No, it is not possible to declare the type of the variable in a for loop.
Instead, a more general approach (e.g. applicable also to enumerate()) is to introduce a let binding by destructuring the item inside the body of the loop.
Example:
for e in bytes.iter().enumerate() {
let (i, &item): (usize, &u8) = e; // here
if item == b' ' {
return i;
}
}
If your loop variable happens to be the result of a function call that returns a generic type:
let input = ["1", "two", "3"];
for v in input.iter().map(|x| x.parse()) {
println!("{:?}", v);
}
error[E0284]: type annotations required: cannot resolve `<_ as std::str::FromStr>::Err == _`
--> src/main.rs:3:37
|
3 | for v in input.iter().map(|x| x.parse()) {
| ^^^^^
You can use a turbofish to specify the types:
for v in input.iter().map(|x| x.parse::<i32>()) {
// ^^^^^^^
println!("{:?}", v);
}
Or you can use the fully-qualified syntax:
for v in input.iter().map(|x| <i32 as std::str::FromStr>::from_str(x)) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
println!("{:?}", v);
}
See also:
How do I imply the type of the value when there are no type parameters or ascriptions?
There used to be a discussion where this was requested, which was followed up by an actual RFC.
It seems like the discussion was postponed, though, because not enough people really cared about the topic.
Currently, if you absolutely want to annotate, it seems like the best option you have is:
fn main() {
let my_vec: Vec<i32> = vec![-1, 22, -333];
for i in my_vec.iter() {
let _: &i32 = i;
println!("{}", i);
}
}
As you can see, this fails if the type doesn't match:
fn main() {
let my_vec: Vec<i32> = vec![-1, 22, -333];
for i in my_vec.iter() {
let _: &i16 = i;
println!("{}", i);
}
}
--> src/main.rs:4:23
|
4 | let _: &i16 = i;
| ---- ^ expected `i16`, found `i32`
| |
| expected due to this
|
= note: expected reference `&i16`
found reference `&i32`
Of course due to automatic dereferencing this method cannot differentiate between &i32 and &&i32, which might be a problem in some cases:
fn main() {
let my_vec: Vec<i32> = vec![-1, 22, -333];
for i in my_vec.iter() {
let _: &i32 = &i; // Compiles, but the right side is &&i32
println!("{}", i);
}
}
But in general this should bring enough confidence to potential reviewers, in my opinion.
Try casting with as:
for i in 1..100 as i64 {
// ...
}
I've written this question out many times, and have finally realized that my biggest problem is that I don't know how I want to represent this data, and that's making it really hard to reason about the rest of the code.
The way the data is represented in Python:
class LSP():
C_MASK_MAP={
"A":"Ch A",
"B":"Ch B",
"C":"Ch C",
"D":"Ch D",
"T":"Tmpr",
"Y":"Batt",
"L":"Acc"
}
ADC_CHANS= (
"Ch A",
"Ch B",
"Ch C",
"Ch D",
"Tmpr",
"Batt"
)
ADC_MAJORS = (
"Ch A",
"Ch B",
"Ch C",
)
My imaginary Rust code (I realize the names will need updating but are the same here for clarity):
enum C_MASK_MAP {
Ch_A = 'A',
Ch_B = 'B',
Ch_C = 'C',
Ch_D = 'D',
Tmpr = 'T',
Batt = 'Y',
Acc = 'L'
}
//...
let ADC_CHANS = [
C_MASK_MAP::Ch_A,
C_MASK_MAP::Ch_B,
C_MASK_MAP::Ch_C,
C_MASK_MAP::Ch_D,
C_MASK_MAP::Tmpr,
C_MASK_MAP::Batt
];
ADC_MAJORS = [
C_MASK_MAP::Ch_A,
C_MASK_MAP::Ch_B,
C_MASK_MAP::Ch_C,
];
I've considered making C_MASK_MAP a HashMap<char, &'static str>, but then I ran into a huge mess trying not to make a million copies of the strs everywhere and dealing with lifetimes while avoiding making Strings, and the syntactic mess that is a reference to a static str (&&'static str or something).
I think there'd be a real benefit to being able to use an enum (or similar) because the values wouldn't be as big and are more easily interchanged C_MASK_MAP.get(key).expect("invalid key") vs just casting.
Your strings are sentinel values; this is a common pattern in Python, but is not how things should be done in Rust: enums are what such things should be: you’re encoding the legal values in the type system.
You could end up with something like this:
#[derive(Clone, Copy)]
#[repr(u8)]
pub enum Mask {
ChA = b'A',
ChB = b'B',
ChC = b'C',
ChD = b'D',
Tmpr = b'T',
Batt = b'Y',
Acc = b'L',
}
// e.g. Mask::ChA.into() == 'A'
impl Into<char> for Mask {
fn into(self) -> char {
self as u8 as char
}
}
impl Mask {
// e.g. Mask::from('A') == Ok(Mask::ChA)
pub fn from(c: char) -> Result<Mask, ()> {
match c {
'A' => Ok(Mask::ChA),
'B' => Ok(Mask::ChB),
'C' => Ok(Mask::ChC),
'D' => Ok(Mask::ChD),
'T' => Ok(Mask::Tmpr),
'Y' => Ok(Mask::Batt),
'L' => Ok(Mask::Acc),
_ => Err(()),
}
}
// e.g. Mask::ChA.is_chan() == true
pub fn is_chan(&self) -> bool {
match *self {
Mask::ChA | Mask::ChB | Mask::ChC | Mask::ChD | Mask::Tmpr | Mask::Batt => true,
Mask::Acc => false,
}
}
// e.g. Mask::ChD.is_major() == false
pub fn is_major(&self) -> bool {
match *self {
Mask::ChA | Mask::ChB | Mask::ChC => true,
Mask::ChD | Mask::Tmpr | Mask::Batt | Mask::Acc => false,
}
}
}
If you wanted you could implement std::str::FromStr for Mask as well, which would allow "A".parse() == Ok(Mask::ChA):
impl FromStr for Mask {
type Err = ();
fn from_str(s: &str) -> Result<Mask, ()> {
match s {
"A" => Ok(Mask::ChA),
"B" => Ok(Mask::ChB),
"C" => Ok(Mask::ChC),
"D" => Ok(Mask::ChD),
"T" => Ok(Mask::Tmpr),
"Y" => Ok(Mask::Batt),
"L" => Ok(Mask::Acc),
_ => Err(()),
}
}
}
I suspect that is_chan et al. may be more suitable than ADC_CHANS et al., but if you do actually need them, they work fine (you could do [Mask; 6] too, but if you need to add new elements it’d change the type which is an API compatibility break if public):
pub static ADC_CHANS: &'static [Mask] = &[
Mask::ChA,
Mask::ChB,
Mask::ChC,
Mask::ChD,
Mask::Tmpr,
Mask::Batt,
];
pub static ADC_MAJORS: &'static [Mask] = &[
Mask::ChA,
Mask::ChB,
Mask::ChC,
];
Copying a &'static str (i.e. copying the reference only) has no cost. A deep copy of the string would be a clone and would be typed as a String.
If &'static str is too verbose for you, you can always define a type alias.
type Str = &'static str;
HashMap<char, &'static str> corresponds nicely to your original map. However, if you don't need the full range of char for the key and you don't actually need to have the value typed as a char anywhere besides indexing the map, you should use an enum instead, as that will restrict the legal values that can be used as keys.