How to implement specification pattern in Rust? - rust

I'm wondering what is the idiomatic way to create a specification pattern in Rust?
Let's say there is a WorkingDay struct and two specifications should be created
IsActiveWorkingDaySpecification
IsInFutureWorkingDaySpecifictaion
My current approach looks like this
struct WorkingDay {
id: uuid::Uuid,
date: chrono::NaiveDate,
is_active: bool,
}
trait Specification<T> {
fn is_satisfied_by(&self, candidate: &T) -> bool;
}
struct IsActiveWorkingDaySpecification;
impl Specification<WorkingDay> for IsActiveWorkingDaySpecification {
fn is_satisfied_by(&self, candidate: &WorkingDay) -> bool {
candidate.is_active == true
}
}
struct IsInFutureWorkingDaySpecification;
impl Specification<WorkingDay> for IsInFutureWorkingDaySpecification {
fn is_satisfied_by(&self, candidate: &WorkingDay) -> bool {
chrono::Utc::now().date().naive_utc() < candidate.date
}
}
fn main() {
let working_day = WorkingDay {
id: uuid::Uuid::new_v4(),
date: chrono::NaiveDate::from_ymd(2077, 11, 24),
is_active: true,
};
let is_active_working_day_specification = IsActiveWorkingDaySpecification {};
let is_future_working_day_specification = IsInFutureWorkingDaySpecification {};
let is_active = is_active_working_day_specification.is_satisfied_by(&working_day);
let is_in_future = is_future_working_day_specification.is_satisfied_by(&working_day);
println!("IsActive: {}", is_active);
println!("IsInFuture: {}", is_in_future);
}
The problem with this code is that the specifications cannot be composed. That is, if specification FutureActiveWorkingDaySpecification needs to be created it forces manually compare results of existing specifications
// cut
fn main () {
// cut
let is_active_working_day_specification = IsActiveWorkingDaySpecification {};
let is_future_working_day_specification = IsInFutureWorkingDaySpecification {};
let is_active = is_active_working_day_specification.is_satisfied_by(&working_day);
let is_in_future = is_future_working_day_specification.is_satisfied_by(&working_day);
let is_active_and_in_future = is_active && is_in_future; // AndSpecification
let is_active_or_in_future = is_active || is_in_future; // OrSpecification
// cut
}
I would like to achieve something like this, but don't know how
// cut
fn main () {
// cut
let is_active_working_day_specification = IsActiveWorkingDaySpecification {};
let is_future_working_day_specification = IsInFutureWorkingDaySpecification {};
// AndSpecification
let is_active_and_in_future = is_active_working_day_specification
.and(is_future_working_day_specification)
.is_satisfied_by(&working_day);
// OrSpecification
let is_active_or_in_future = is_active_working_day_specification
.or(is_future_working_day_specification)
.is_satisfied_by(&working_day);
// cut
}

This can be done by creating OrSpecification and AndSpecification structs which implement the Specification<T> trait, and then extending Specification<T> to include or and and methods with default implementations which create these:
trait Specification<T>: Sized {
fn is_satisfied_by(&self, candidate: &T) -> bool;
fn and<S>(self, other: S) -> AndSpecification<Self, S> {
AndSpecification {
a: self,
b: other,
}
}
fn or<S>(self, other: S) -> OrSpecification<Self, S> {
OrSpecification {
a: self,
b: other,
}
}
}
struct AndSpecification<A, B> {
a: A,
b: B,
}
impl<T, A, B> Specification<T> for AndSpecification<A, B>
where A: Specification<T>,
B: Specification<T>,
{
fn is_satisfied_by(&self, candidate: &T) -> bool {
self.a.is_satisfied_by(candidate) && self.b.is_satisfied_by(candidate)
}
}
struct OrSpecification<A, B> {
a: A,
b: B,
}
impl<T, A, B> Specification<T> for OrSpecification<A, B>
where A: Specification<T>,
B: Specification<T>,
{
fn is_satisfied_by(&self, candidate: &T) -> bool {
self.a.is_satisfied_by(candidate) || self.b.is_satisfied_by(candidate)
}
}
Note that the requirement of Specification<T> to implement the Sized trait is avoidable, it would just require using Box in various places. For the sake of clarity here I've just chosen the simpler option.

You can add default methods to the Specification trait that return combination structs, eg AndCombination
trait Specification<T> {
fn is_satisfied_by(&self, candidate: &T) -> bool;
fn and<O: Specification<T>>(self, other: O) -> AndCombination<Self, O>
where Self: Sized {
AndCombination(self, other)
}
}
struct AndCombination<L, R>(L,R);
impl<T, L, R> Specification<T> for AndCombination<L,R>
where L: Specification<T>
R: Specification<T>
{
fn is_satisfied_by(&self, candidate: &T) -> bool {
self.0.is_satisfied_by(candidate) && self.1.is_satisfied_by(candidate)
}
}
These can then be combined to create composite specifications
let active_future = IsActive.and(IsInFuture);
println!("Day is active in future: {}", active_future.is_satisfied_by(&working_day));
Similar structs can be created for Or and Not.
Playground Example here

Related

How to support two visitors which returns different return value types in Rust?

I am trying to implement a Visitor pattern in Rust.
I am not able to find a way to support two visitors which return different return value types.
Playground link
trait Visited<R> {
fn accept (self: &Self, v: &dyn Visitor<R>) -> R;
}
trait Visitor<R> {
fn visit_a(&self, a: &A<R>) -> R;
fn visit_b(&self, b: &B) -> R;
}
I've implemented two data structures that can be visited.
// ---- A ----
struct A<R> {
value: String,
b: Box<dyn Visited<R>>,
}
impl<R> Visited<R> for A<R> {
fn accept (&self, v: &dyn Visitor<R>) -> R {
v.visit_a(self)
}
}
// ---- B ----
struct B {
value: i32,
}
impl<R> Visited<R> for B {
fn accept(&self, v: &dyn Visitor<R>) -> R {
v.visit_b(self)
}
}
This worked okay when I just had a concrete visitor.
struct Visitor1 {}
impl Visitor<String> for Visitor1 {
fn visit_a(&self, a: &A<String>) -> String {
let b = a.b.accept(self);
format!("visitor1.visit_a(): {} {}", a.value, b)
}
fn visit_b(&self, b: &B) -> String {
format!("visitor1.visit_b(): {}", b.value)
}
}
However, the whole point of the Visitor pattern is to let multiple algorithm to be applied against the data structure.
When I wanted to add another visitor, I couldn't figure out how to make it work.
struct Visitor2 {}
impl Visitor<i32> for Visitor2 {
fn visit_a(&self, a: &A<i32>) -> i32 {
123
}
fn visit_b(&self, b: &B) -> i32 {
456
}
}
fn main() {
let a = A {
value: "HELLO".to_string(),
b: Box::new(B{ value: 32 })
};
let v1 = Visitor1{};
let s: String = a.accept(&v1);
println!("{}", s);
let v2 = Visitor2{};
let v: i32 = a.accept(&v2);
println!("{}", v);
}
The type of a is inferred as A<String>, and the a.accept(&v2) caused a type mismatch error.
I'd like to tell a to be visited by Visitor1 and Visitor2. How can I do this?
If you return only 'static types, you can use type erasure. The idea is to create a trait, ErasedVisitor, that is not generic and instead return Box<dyn Any>, and implement this trait for all Visitors and use it internally. This mean, though, that you cannot use a generic parameter, only an associated type (otherwise you get "unconstrained type parameter":
trait Visited {
fn accept_dyn(&self, v: &dyn ErasedVisitor) -> Box<dyn Any>;
fn accept<V: Visitor>(&self, v: &V) -> V::Result
where
Self: Sized,
{
*self.accept_dyn(v).downcast().unwrap()
}
}
impl<T: ?Sized + Visited> Visited for &'_ T {
fn accept_dyn(&self, v: &dyn ErasedVisitor) -> Box<dyn Any> {
T::accept_dyn(&**self, v)
}
}
impl<T: ?Sized + Visited> Visited for &'_ mut T {
fn accept_dyn(&self, v: &dyn ErasedVisitor) -> Box<dyn Any> {
T::accept_dyn(&**self, v)
}
}
impl<T: ?Sized + Visited> Visited for Box<T> {
fn accept_dyn(&self, v: &dyn ErasedVisitor) -> Box<dyn Any> {
T::accept_dyn(&**self, v)
}
}
trait Visitor {
type Result: 'static;
fn visit_a(&self, a: &A) -> Self::Result;
fn visit_b(&self, b: &B) -> Self::Result;
}
trait ErasedVisitor {
fn visit_a(&self, a: &A) -> Box<dyn Any>;
fn visit_b(&self, b: &B) -> Box<dyn Any>;
}
impl<V: ?Sized + Visitor> ErasedVisitor for V {
fn visit_a(&self, a: &A) -> Box<dyn Any> {
Box::new(<Self as Visitor>::visit_a(self, a))
}
fn visit_b(&self, b: &B) -> Box<dyn Any> {
Box::new(<Self as Visitor>::visit_b(self, b))
}
}
struct A {
value: String,
b: Box<dyn Visited>,
}
impl Visited for A {
fn accept_dyn(&self, v: &dyn ErasedVisitor) -> Box<dyn Any> {
v.visit_a(self)
}
}
Playground.
But if you can, the best way is to use static polymorphism (generics) instead of dynamic dispatch.
If you want your Visited trait to support multiple return types, you cannot tie the return type to the object itself. Currently, the return type is a generic, so every object has the return type hard-coded into it.
To solve this, you can remove the return type generic from the Visited trait and instead attach it to the accept function.
This has one drawback though: You can no longer create trait objects with this trait. That makes sense, because once cast to a dyn Visited, Rust no longer knows which type it is, making it impossible for the compiler to compile the accept function for all required types. It looses the knowledge between the compilated function and the types that it will get called with.
Normally, this is fine, but in your case, A holds a Box<dyn Visited>. This is impossible, due to the reasons described above.
So if we change Box<dyn Visited> to B, (and do some refactoring with the generics), we can get it to work:
trait Visited {
fn accept<V: Visitor>(&self, v: &V) -> V::Ret;
}
trait Visitor {
type Ret;
fn visit_a(&self, a: &A) -> Self::Ret;
fn visit_b(&self, b: &B) -> Self::Ret;
}
// ---- A ----
struct A {
value: String,
b: B,
}
impl Visited for A {
fn accept<V: Visitor>(&self, v: &V) -> V::Ret {
v.visit_a(self)
}
}
// ---- B ----
struct B {
value: i32,
}
impl Visited for B {
fn accept<V: Visitor>(&self, v: &V) -> V::Ret {
v.visit_b(self)
}
}
struct Visitor1 {}
impl Visitor for Visitor1 {
type Ret = String;
fn visit_a(&self, a: &A) -> String {
let b = a.b.accept(self);
format!("visitor1.visit_a(): {} {}", a.value, b)
}
fn visit_b(&self, b: &B) -> String {
format!("visitor1.visit_b(): {}", b.value)
}
}
struct Visitor2 {}
impl Visitor for Visitor2 {
type Ret = i32;
fn visit_a(&self, _a: &A) -> i32 {
123
}
fn visit_b(&self, _b: &B) -> i32 {
456
}
}
fn main() {
let a = A {
value: "HELLO".to_string(),
b: B { value: 32 },
};
let v1 = Visitor1 {};
let s: String = a.accept(&v1);
println!("{}", s);
let v2 = Visitor2 {};
let v: i32 = a.accept(&v2);
println!("{}", v);
}
visitor1.visit_a(): HELLO visitor1.visit_b(): 32
123
We can make this a little more convenient and make the actual type a generic of A. In contrast to a dyn Visited, this allows Rust to resolve its exact type at compile time, making it possible to compile it.
trait Visited {
fn accept<V: Visitor>(&self, v: &V) -> V::Ret;
}
trait Visitor {
type Ret;
fn visit_a<T: Visited>(&self, a: &A<T>) -> Self::Ret;
fn visit_b(&self, b: &B) -> Self::Ret;
}
// ---- A ----
struct A<T> {
value: String,
b: T,
}
impl<T: Visited> Visited for A<T> {
fn accept<V: Visitor>(&self, v: &V) -> V::Ret {
v.visit_a(self)
}
}
// ---- B ----
struct B {
value: i32,
}
impl Visited for B {
fn accept<V: Visitor>(&self, v: &V) -> V::Ret {
v.visit_b(self)
}
}
struct Visitor1 {}
impl Visitor for Visitor1 {
type Ret = String;
fn visit_a<T: Visited>(&self, a: &A<T>) -> String {
let b = a.b.accept(self);
format!("visitor1.visit_a(): {} {}", a.value, b)
}
fn visit_b(&self, b: &B) -> String {
format!("visitor1.visit_b(): {}", b.value)
}
}
struct Visitor2 {}
impl Visitor for Visitor2 {
type Ret = i32;
fn visit_a<T: Visited>(&self, _a: &A<T>) -> i32 {
123
}
fn visit_b(&self, _b: &B) -> i32 {
456
}
}
fn main() {
let a = A {
value: "HELLO".to_string(),
b: B { value: 32 },
};
let v1 = Visitor1 {};
let s: String = a.accept(&v1);
println!("{}", s);
let v2 = Visitor2 {};
let v: i32 = a.accept(&v2);
println!("{}", v);
}
visitor1.visit_a(): HELLO visitor1.visit_b(): 32
123

What pattern to utilize to use a Vec of differing nested generic types/trait objects?

I'm trying to implement a pattern where different Processors can dictate the input type they take and produce a unified output (currently a fixed type, but I'd like to get it generic once this current implementation is working).
Below is a minimal example:
use std::convert::From;
use processor::NoOpProcessor;
use self::{
input::{Input, InputStore},
output::UnifiedOutput,
processor::{MultiplierProcessor, Processor, StringProcessor},
};
mod input {
use std::collections::HashMap;
#[derive(Debug)]
pub struct Input<T>(pub T);
#[derive(Default)]
pub struct InputStore(HashMap<String, String>);
impl InputStore {
pub fn insert<K, V>(mut self, key: K, value: V) -> Self
where
K: ToString,
V: ToString,
{
let key = key.to_string();
let value = value.to_string();
self.0.insert(key, value);
self
}
pub fn get<K, V>(&self, key: K) -> Option<Input<V>>
where
K: ToString,
for<'a> &'a String: Into<V>,
{
let key = key.to_string();
self.0.get(&key).map(|value| Input(value.into()))
}
}
}
mod processor {
use super::{input::Input, output::UnifiedOutput};
use super::I32Input;
pub struct NoOpProcessor;
pub trait Processor {
type I;
fn process(&self, input: &Input<Self::I>) -> UnifiedOutput;
}
impl Processor for NoOpProcessor {
type I = I32Input;
fn process(&self, input: &Input<Self::I>) -> UnifiedOutput {
UnifiedOutput(input.0 .0)
}
}
pub struct MultiplierProcessor(pub i32);
impl Processor for MultiplierProcessor {
type I = I32Input;
fn process(&self, input: &Input<Self::I>) -> UnifiedOutput {
UnifiedOutput(input.0 .0 * self.0)
}
}
pub struct StringProcessor;
impl Processor for StringProcessor {
type I = String;
fn process(&self, input: &Input<Self::I>) -> UnifiedOutput {
UnifiedOutput(input.0.parse().unwrap())
}
}
}
mod output {
#[derive(Debug)]
pub struct UnifiedOutput(pub i32);
}
pub fn main() {
let input_store = InputStore::default()
.insert("input_a", 123)
.insert("input_b", 567)
.insert("input_c", "789");
let processors = {
let mut labelled_processors = Vec::new();
// let mut labelled_processors: Vec<LabelledProcessor<Input<>>> = Vec::new(); // What's the correct type?
labelled_processors.push(LabelledProcessor("input_a", Box::new(NoOpProcessor)));
labelled_processors.push(LabelledProcessor(
"input_b",
Box::new(MultiplierProcessor(3)),
));
// labelled_processors.push(LabelledProcessor("input_c", Box::new(StringProcessor)));
labelled_processors
};
for processor in processors {
let output = retrieve_input_and_process(&input_store, processor);
println!("{:?}", output);
}
}
#[derive(Debug)]
pub struct I32Input(pub i32);
impl From<&String> for I32Input {
fn from(s: &String) -> Self {
Self(s.parse().unwrap())
}
}
struct LabelledProcessor<I>(&'static str, Box<dyn Processor<I = I>>)
where
for<'a> &'a String: Into<I>;
fn retrieve_input_and_process<T>(
store: &InputStore,
processor: LabelledProcessor<T>,
) -> UnifiedOutput
where
for<'a> &'a String: Into<T>,
{
let input = store.get(processor.0).unwrap();
processor.1.process(&input)
}
When // labelled_processors.push(LabelledProcessor("input_c", Box::new(StringProcessor))); is uncommented, I get the below compilation error:
error[E0271]: type mismatch resolving `<attempt2::processor::StringProcessor as attempt2::processor::Processor>::I == attempt2::I32Input`
--> src/attempt2.rs:101:63
|
101 | labelled_processors.push(LabelledProcessor("input_c", Box::new(StringProcessor)));
| ^^^^^^^^^^^^^^^^^^^^^^^^^ type mismatch resolving `<attempt2::processor::StringProcessor as attempt2::processor::Processor>::I == attempt2::I32Input`
|
note: expected this to be `attempt2::I32Input`
--> src/attempt2.rs:75:18
|
75 | type I = String;
| ^^^^^^
= note: required for the cast from `attempt2::processor::StringProcessor` to the object type `dyn attempt2::processor::Processor<I = attempt2::I32Input>`
I think I've learnt enough to "get" what the issue is - the labelled_processors vec expects all its items to have the same type. My problem is I'm unsure how to rectify this. I've tried to leverage dynamic dispatch more (for example changing LabelledProcessor to struct LabelledProcessor(&'static str, Box<dyn Processor<dyn Input>>);). However these changes spiral to their own issues with the type system too.
Other answers I've found online generally don't address this level of complexity with respect to the nested generics/traits - stopping at 1 level with the answer being let vec_x: Vec<Box<dyn SomeTrait>> .... This makes me wonder if there's an obvious answer that can be reached that I've just missed or if there's a whole different pattern I should be employing instead to achieve this goal?
I'm aware of potentially utilizing enums as wel, but that would mean all usecases would need to be captured within this module and it may not be able to define inputs/outputs/processors in external modules.
A bit lost at this point.
--- EDIT ---
Some extra points:
This is just an example, so things like InputStore basically converting everything to String is just an implementation detail. It's mainly to symbolize the concept of "the type needs to comply with some trait to be accepted", I just chose String for simplicity.
One possible solution would be to make retrieve_input_and_process a method of LabelledProcessor, and then hide the type behind a trait:
use std::convert::From;
use processor::NoOpProcessor;
use self::{
input::InputStore,
output::UnifiedOutput,
processor::{MultiplierProcessor, Processor, StringProcessor},
};
mod input {
use std::collections::HashMap;
#[derive(Debug)]
pub struct Input<T>(pub T);
#[derive(Default)]
pub struct InputStore(HashMap<String, String>);
impl InputStore {
pub fn insert<K, V>(mut self, key: K, value: V) -> Self
where
K: ToString,
V: ToString,
{
let key = key.to_string();
let value = value.to_string();
self.0.insert(key, value);
self
}
pub fn get<K, V>(&self, key: K) -> Option<Input<V>>
where
K: ToString,
for<'a> &'a str: Into<V>,
{
let key = key.to_string();
self.0.get(&key).map(|value| Input(value.as_str().into()))
}
}
}
mod processor {
use super::{input::Input, output::UnifiedOutput};
use super::I32Input;
pub struct NoOpProcessor;
pub trait Processor {
type I;
fn process(&self, input: &Input<Self::I>) -> UnifiedOutput;
}
impl Processor for NoOpProcessor {
type I = I32Input;
fn process(&self, input: &Input<Self::I>) -> UnifiedOutput {
UnifiedOutput(input.0 .0)
}
}
pub struct MultiplierProcessor(pub i32);
impl Processor for MultiplierProcessor {
type I = I32Input;
fn process(&self, input: &Input<Self::I>) -> UnifiedOutput {
UnifiedOutput(input.0 .0 * self.0)
}
}
pub struct StringProcessor;
impl Processor for StringProcessor {
type I = String;
fn process(&self, input: &Input<Self::I>) -> UnifiedOutput {
UnifiedOutput(input.0.parse().unwrap())
}
}
}
mod output {
#[derive(Debug)]
pub struct UnifiedOutput(pub i32);
}
pub fn main() {
let input_store = InputStore::default()
.insert("input_a", 123)
.insert("input_b", 567)
.insert("input_c", "789");
let processors = {
let mut labelled_processors: Vec<Box<dyn LabelledProcessorRef>> = Vec::new();
labelled_processors.push(Box::new(LabelledProcessor(
"input_a",
Box::new(NoOpProcessor),
)));
labelled_processors.push(Box::new(LabelledProcessor(
"input_b",
Box::new(MultiplierProcessor(3)),
)));
labelled_processors.push(Box::new(LabelledProcessor(
"input_c",
Box::new(StringProcessor),
)));
labelled_processors
};
for processor in processors {
let output = processor.retrieve_input_and_process(&input_store);
println!("{:?}", output);
}
}
#[derive(Debug)]
pub struct I32Input(pub i32);
impl From<&str> for I32Input {
fn from(s: &str) -> Self {
Self(s.parse().unwrap())
}
}
struct LabelledProcessor<I>(&'static str, Box<dyn Processor<I = I>>);
impl<I> LabelledProcessorRef for LabelledProcessor<I>
where
for<'a> &'a str: Into<I>,
{
fn retrieve_input_and_process(&self, store: &InputStore) -> UnifiedOutput {
let input = store.get(self.0).unwrap();
self.1.process(&input)
}
}
trait LabelledProcessorRef {
fn retrieve_input_and_process(&self, store: &InputStore) -> UnifiedOutput;
}
UnifiedOutput(123)
UnifiedOutput(1701)
UnifiedOutput(789)

No method named `match_command` found for struct `HashMap` in the current scope

I'm writing a command parser. Here is my code.
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
struct Command {
begin: String,
command: String,
args: Vec<String>
}
impl Command {
fn parse(command: &str) -> Self {
let command: Vec<&str> = command.split(" ").collect();
Command {
begin: command[0][0..1].to_string(),
command: command[0][1..].to_string(),
args: command[1..].iter()
.map(|x| x.to_string())
.map(|x| x.replace("%s", " "))
.map(|x| x.replace("%n", "\n"))
.collect()
}
}
fn prefix(&self) -> String {
format!("{}{}", self.begin, self.command)
}
}
impl ToString for Command {
fn to_string(&self) -> String {
let mut string = format!("{}{}", self.begin, self.command);
for i in self.args {
string.push_str(format!(" {}", i).as_str())
}
string
}
}
trait CommandRegister {
fn match_command(&self, command: &str) -> Option<fn(args: Vec<String>)>;
}
impl CommandRegister for HashMap<Command, fn(args: Vec<String>)> {
fn match_command(&self, command: &str) -> Option<fn(args: Vec<String>)> {
let command = Command::parse(command);
for i in self {
if *i.0 == command {
return Some(*i.1)
}
}
None
}
}
impl PartialEq<Self> for Command {
fn eq(&self, other: &Self) -> bool {
self.begin == other.begin && self.command == other.command && self.args.len() == other.args.len()
}
}
impl Eq for Command {
}
impl Hash for Command {
fn hash<H: Hasher>(&self, state: &mut H) {
self.to_string().hash(state)
}
}
fn main() {
let command = Command::parse("/alpha do 1 2");
println!("Begin: {}", command.begin);
println!("Command: {}", command.command);
for i in 0..command.args.len() {
println!("Arg{i}: {}", command.args[i])
}
let mut map: HashMap<&Command, fn(args: Vec<String>)> = HashMap::new();
let command = Command {
begin: String::from("/"),
command: String::from("alpha"),
args: vec!("arg1".to_string(), "arg2".to_string())
};
map.insert(&command, |args: Vec<String>| {
println!("Arguments length: {}", args.len());
});
match map.match_command(command.to_string()) {
Some(func) => func(command.args)
}
}
The match_command gave me an error, it said
no method named match_commandfound for structHashMap in the current scope
But I implemented CommandRegister trait for HashMap in the scope. I think perhaps it is because of the type convert, but I have no idea how to deal with it. And I also want to ask about the difference between fn and Closure. If I want to use closure here, is it ok?
The implementation isn't found because the types don't match. Compare:
impl CommandRegister for HashMap<Command, fn(args: Vec<String>)> {
let mut map: HashMap<&Command, fn(args: Vec<String>)> = HashMap::new();
One uses Command and the other uses &Command.
However, note that you are using the HashMap very inefficiently:
for i in self {
if *i.0 == command {
return Some(*i.1)
}
}
This performs a linear search over the contents. The whole point of using a HashMap is to look up items by their hash instead of doing a linear search:
fn match_command(&self, command: &str) -> Option<fn(args: Vec<String>)> {
let command = Command::parse(command);
self.get(&command).copied()
}
If this doesn't work, it's because your Hash and PartialEq implementations compare different things:
impl PartialEq<Self> for Command {
fn eq(&self, other: &Self) -> bool {
self.begin == other.begin && self.command == other.command && self.args.len() == other.args.len()
}
}
impl Hash for Command {
fn hash<H: Hasher>(&self, state: &mut H) {
self.to_string().hash(state)
}
}
Note that the PartialEq implementation only compares the lengths of the vectors. To agree with your Hash implementation, it should compare exactly what Hash looks at:
impl PartialEq<Self> for Command {
fn eq(&self, other: &Self) -> bool {
self.to_string() == other.to_string()
}
}
The easiest way to handle this would be to remove your own implementations of Eq, PartialEq, and Hash, and instead derive them on your struct:
#[derive(PartialEq, Eq, Hash)]
struct Command {
This will generate implementations for you that look at all struct fields automatically. The generated Hash implementation should perform better than yours, but -- more importantly -- all of the implementations will be correct.

How to create a single threaded singleton in Rust?

I'm currently trying to wrap a C library in rust that has a few requirements. The C library can only be run on a single thread, and can only be initialized / cleaned up once on the same thread. I want something something like the following.
extern "C" {
fn init_lib() -> *mut c_void;
fn cleanup_lib(ctx: *mut c_void);
}
// This line doesn't work.
static mut CTX: Option<(ThreadId, Rc<Context>)> = None;
struct Context(*mut c_void);
impl Context {
fn acquire() -> Result<Rc<Context>, Error> {
// If CTX has a reference on the current thread, clone and return it.
// Otherwise initialize the library and set CTX.
}
}
impl Drop for Context {
fn drop(&mut self) {
unsafe { cleanup_lib(self.0); }
}
}
Anyone have a good way to achieve something like this? Every solution I try to come up with involves creating a Mutex / Arc and making the Context type Send and Sync which I don't want as I want it to remain single threaded.
A working solution I came up with was to just implement the reference counting myself, removing the need for Rc entirely.
#![feature(once_cell)]
use std::{error::Error, ffi::c_void, fmt, lazy::SyncLazy, sync::Mutex, thread::ThreadId};
extern "C" {
fn init_lib() -> *mut c_void;
fn cleanup_lib(ctx: *mut c_void);
}
#[derive(Debug)]
pub enum ContextError {
InitOnOtherThread,
}
impl fmt::Display for ContextError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
ContextError::InitOnOtherThread => {
write!(f, "Context already initialized on a different thread")
}
}
}
}
impl Error for ContextError {}
struct StaticPtr(*mut c_void);
unsafe impl Send for StaticPtr {}
static CTX: SyncLazy<Mutex<Option<(ThreadId, usize, StaticPtr)>>> =
SyncLazy::new(|| Mutex::new(None));
pub struct Context(*mut c_void);
impl Context {
pub fn acquire() -> Result<Context, ContextError> {
let mut ctx = CTX.lock().unwrap();
if let Some((id, ref_count, ptr)) = ctx.as_mut() {
if *id == std::thread::current().id() {
*ref_count += 1;
return Ok(Context(ptr.0));
}
Err(ContextError::InitOnOtherThread)
} else {
let ptr = unsafe { init_lib() };
*ctx = Some((std::thread::current().id(), 1, StaticPtr(ptr)));
Ok(Context(ptr))
}
}
}
impl Drop for Context {
fn drop(&mut self) {
let mut ctx = CTX.lock().unwrap();
let (_, ref_count, ptr) = ctx.as_mut().unwrap();
*ref_count -= 1;
if *ref_count == 0 {
unsafe {
cleanup_lib(ptr.0);
}
*ctx = None;
}
}
}
I think the most 'rustic' way to do this is with std::sync::mpsc::sync_channel and an enum describing library operations.
The only public-facing elements of this module are launch_lib(), the SafeLibRef struct (but not its internals), and the pub fn that are part of the impl SafeLibRef.
Also, this example strongly represents the philosophy that the best way to deal with global state is to not have any.
I have played fast and loose with the Result::unwrap() calls. It would be more responsible to handle error conditions better.
use std::sync::{ atomic::{ AtomicBool, Ordering }, mpsc::{ SyncSender, Receiver, sync_channel } };
use std::ffi::c_void;
extern "C" {
fn init_lib() -> *mut c_void;
fn do_op_1(ctx: *mut c_void, a: u16, b: u32, c: u64) -> f64;
fn do_op_2(ctx: *mut c_void, a: f64) -> bool;
fn cleanup_lib(ctx: *mut c_void);
}
enum LibOperation {
Op1(u16,u32,u64,SyncSender<f64>),
Op2(f64, SyncSender<bool>),
Terminate(SyncSender<()>),
}
#[derive(Clone)]
pub struct SafeLibRef(SyncSender<LibOperation>);
fn lib_thread(rx: Receiver<LibOperation>) {
static LIB_INITIALIZED: AtomicBool = AtomicBool::new(false);
if LIB_INITIALIZED.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst).is_err() {
panic!("Tried to double-initialize library!");
}
let libptr = unsafe { init_lib() };
loop {
let op = rx.recv();
if op.is_err() {
unsafe { cleanup_lib(libptr) };
break;
}
match op.unwrap() {
LibOperation::Op1(a,b,c,tx_res) => {
let res: f64 = unsafe { do_op_1(libptr, a, b, c) };
tx_res.send(res).unwrap();
},
LibOperation::Op2(a, tx_res) => {
let res: bool = unsafe { do_op_2(libptr, a) };
tx_res.send(res).unwrap();
}
LibOperation::Terminate(tx_res) => {
unsafe { cleanup_lib(libptr) };
tx_res.send(()).unwrap();
break;
}
}
}
}
/// This needs to be called no more than once.
/// The resulting SafeLibRef can be cloned and passed around.
pub fn launch_lib() -> SafeLibRef {
let (tx,rx) = sync_channel(0);
std::thread::spawn(|| lib_thread(rx));
SafeLibRef(tx)
}
// This is the interface that most of your code will use
impl SafeLibRef {
pub fn op_1(&self, a: u16, b: u32, c: u64) -> f64 {
let (res_tx, res_rx) = sync_channel(1);
self.0.send(LibOperation::Op1(a, b, c, res_tx)).unwrap();
res_rx.recv().unwrap()
}
pub fn op_2(&self, a: f64) -> bool {
let (res_tx, res_rx) = sync_channel(1);
self.0.send(LibOperation::Op2(a, res_tx)).unwrap();
res_rx.recv().unwrap()
}
pub fn terminate(&self) {
let (res_tx, res_rx) = sync_channel(1);
self.0.send(LibOperation::Terminate(res_tx)).unwrap();
res_rx.recv().unwrap();
}
}

How can I override all the fields in a mutable reference using another struct?

How can I avoid listing all the fields when using x to populate input?
struct StructX {
a: u32,
b: u32,
}
trait TraitY {
fn foo(info: &mut StructX) -> bool;
}
impl TraitY for SomeZ {
fn foo(input: &mut StructX) -> bool {
let mut x = StructX { /*....*/ };
// do something with x, then finally:
input.a = x.a;
input.b = x.b;
}
}
In C++ it would be just input = x, but that doesn't work in Rust. Note that this is an "interface" so I cannot change the type of input to something else.
You have to dereference input (playground):
struct StructX {
a: u32,
b: u32,
}
trait TraitY {
fn foo(info: &mut StructX) -> bool;
}
impl TraitY for SomeZ {
fn foo(input: &mut StructX) -> bool {
let mut x = StructX { /*....*/ };
// do something with x, then finally:
*input = x;
return true;
}
}
If you wouldn't like to move x into input then you can use Clone::clone_from
playground
#[derive(Clone)]
struct StructX {
a: u32,
b: u32,
}
trait TraitY {
fn foo(info: &mut StructX) -> bool;
}
struct SomeZ{}
impl TraitY for SomeZ {
fn foo(input: &mut StructX) -> bool {
let mut x = StructX { a:42, b:56};
x.a = 43;
input.clone_from(&x);
return true;
}
}

Resources