Design patterns without the box - rust

Rust beginner here. I have a number of algorithms that are almost identical but, at the final step, they all aggregate the results in slightly differently ways. Let's say the Algorithm does the following:
pub struct Algorithm<T> {
result_aggregator: Box<dyn ResultAggregator<T>>,
}
impl<T> Algorithm<T> {
pub fn calculate(&self, num1: i32, num2: i32) -> T {
let temp = num1 + num2;
self.result_aggregator.create(temp)
}
}
With this, I can create a few different result aggregator classes to take my temp result and transform it into my final result:
pub trait ResultAggregator<T> {
fn create(&self, num: i32) -> T;
}
pub struct FloatAggregator;
pub struct StringAggregator;
impl ResultAggregator<f32> for FloatAggregator {
fn create(&self, num: i32) -> f32 {
num as f32 * 3.14159
}
}
impl ResultAggregator<String> for StringAggregator {
fn create(&self, num: i32) -> String {
format!("~~{num}~~")
}
}
...and call it like so:
fn main() {
// Here's a float example
let aggregator = FloatAggregator;
let algorithm = Algorithm {
result_aggregator: Box::new(aggregator),
};
let result = algorithm.calculate(4, 5);
println!("The result has value {result}");
// Here's a string example
let aggregator = StringAggregator;
let algorithm = Algorithm {
result_aggregator: Box::new(aggregator),
};
let result = algorithm.calculate(4, 5);
println!("The result has value {result}");
}
This is what I've come up with.
Question: Is it possible to do this without the dynamic box? It's performance critical and I understand that generics are usually a good solution but I've had no luck figuring out how to get it working without dynamic dispatch.
So what's the Rusty solution to this problem? I feel like I'm approaching it with my C# hat on which is probably not the way to go.
Link to the playground

You can use an associated type instead of a generic parameter:
pub trait ResultAggregator {
type Output;
fn create(&self, num: i32) -> Self::Output;
}
pub struct FloatAggregator;
pub struct StringAggregator;
impl ResultAggregator for FloatAggregator {
type Output = f32;
fn create(&self, num: i32) -> f32 {
num as f32 * 3.14159
}
}
impl ResultAggregator for StringAggregator {
type Output = String;
fn create(&self, num: i32) -> String {
format!("~~{num}~~")
}
}
pub struct Algorithm<Aggregator> {
result_aggregator: Aggregator,
}
impl<Aggregator: ResultAggregator> Algorithm<Aggregator> {
pub fn calculate(&self, num1: i32, num2: i32) -> Aggregator::Output {
let temp = num1 + num2;
self.result_aggregator.create(temp)
}
}

Related

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)

Determine fn output type from input type

I would like a function that returns f32 if the input is f32, for all other numeric inputs it should return f64.
A simplified example of the structure is this:
use num::{Num, NumCast, ToPrimitive, traits::Float};
fn example<N: Num + ToPrimitive, T: Float>(input: N) -> T {
let output = input + N::one();
NumCast::from(output).unwrap()
}
fn main() {
println!("{}", example::<f32, f32>(1f32));
println!("{}", example::<u32, f64>(1u32));
}
is there a way to control the dispatch so that I can drop the turbofish and it will automatically map
f32 -> f32 and
anything else -> f64 ?
You cannot have a function which returns either f32 or f64 making decision in runtime. But you can implement such behavior using traits.
You can have two traits ToF32 and ToF64 and implement first one for f32 only and ToF64 for all other numeric types except f32. You will need a lot of boilerplate code to implement ToF64 though. It can be compacted using macros.
use num::{NumCast, ToPrimitive, one, Integer};
trait ToF32 {
fn example(self) -> f32;
}
impl ToF32 for f32 {
fn example(self) -> f32 {
let output = self + one::<Self>();
NumCast::from(output).unwrap()
}
}
trait ToF64 {
fn example(self) -> f64;
}
impl<T: Integer + ToPrimitive> ToF64 for T {
fn example(self) -> f64 {
let output = self + one::<Self>();
NumCast::from(output).unwrap()
}
}
fn main() {
println!("{}", 1f32.example());
println!("{}", 1u32.example());
}
Playground link

How to properly trait object that works with object-unsafe types?

I want to create a vector containing trait objects that have a val method that returns a comparable value:
trait Comparable {
fn val(&self) -> Box<dyn std::cmp::Ord>;
}
struct Int64Array {}
impl Comparable for Int64Array {
fn val(&self, i: usize) -> Box<dyn std::cmp::Ord> {
Box::new(i+1)
}
}
struct StringArray {}
impl Comparable for StringArray {
fn val(&self, i: usize) -> Box<dyn std::cmp::Ord> {
Box::new(format!("foo_{}", i))
}
}
fn main() {
// each row is guaranteed to have vector elements with the same type
let rows: Vec<Box<dyn Comparable>> = vec![
Box::new(Int64Array{}),
Box::new(StringArray{}),
];
println!("{}", rows[0].val(0).cmp(rows[0].val(1)));
println!("{}", rows[1].val(0).cmp(rows[1].val(1)));
}
However, because std::cmp::Ord is not a object-safe trait, this won't compile. What's the recommend way to workaround this limitation?
Thanks everyone for the quick replies.
Combing suggestions from #FrancisGagné and #loganfsmyth, here is a workaround that avoids using Ord trait directly:
trait Comparable {
fn compare(&self, i: usize, j: usize) -> std::cmp::Ordering;
}
struct Int64Array {}
impl Comparable for Int64Array {
fn compare(&self, _i: usize, _j: usize) -> std::cmp::Ordering {
std::cmp::Ordering::Equal
}
}
struct StringArray {}
impl Comparable for StringArray {
fn compare(&self, _i: usize, _j: usize) -> std::cmp::Ordering {
std::cmp::Ordering::Equal
}
}
fn main() {
// each row is guaranteed to have vector elements with the same type
let rows: Vec<Box<dyn Comparable>> = vec![
Box::new(Int64Array{}),
Box::new(StringArray{}),
];
println!("{:#?}", rows[0].compare(0, 1));
println!("{:#?}", rows[1].compare(0, 1));
}
I wonder if there is other ways to implement this feature.

Using Rayon into_par_iter().sum() with custom struct

I'm trying to get use Rayon::prelude::into_par_iter to sum up a bunch of instances of a struct. I've implemented std::iter::Sum for the struct, but am still running into an error. Here is my example code;
use std::iter::Sum;
use rayon::prelude::*;
pub struct TestStruct {
val: f32,
}
impl TestStruct {
pub fn new(v: f32) -> Self {
Self { val: v }
}
}
impl<'a> Sum<&'a Self> for TestStruct {
fn sum<I>(iter: I) -> Self
where
I: Iterator<Item = &'a Self>,
{
iter.fold(Self { val: 0. }, |a, b| Self {
val: a.val + b.val,
})
}
}
fn main() {
let default_val: f32 = 1.1;
let sum: TestStruct = (0..5).into_par_iter().map(|&default_val| {
TestStruct::new(default_val)
})
.sum();
println!("{}", sum.val);
}
and I am told the trait 'std::iter::Sum' is not implemented for 'TestStruct', but I think I am implementing it. Am I missing something here?
Two small compounded issues. One is that the std::iter::Sum is defined as:
pub trait Sum<A = Self> {
fn sum<I>(iter: I) -> Self
where
I: Iterator<Item = A>;
}
The Self here refers to whatever the struct gets the implementation and the default generic parameter has already been filled in. You don't need to specify it any more if implementing Sum for struct itself.
The other is that the map closure parameter default_val, which is an integer, shadows the previous float one with the same name. Since TestStruct::new expects a f32, it leads to a type error.
This works:
impl Sum for TestStruct {
fn sum<I>(iter: I) -> Self
where
I: Iterator<Item = Self>,
{
...
}
}
fn main() {
let default_val: f32 = 1.1;
let sum: TestStruct = (0..5)
.into_par_iter()
.map(|_| TestStruct::new(default_val))
.sum();
println!("{}", sum.val);
}

How to satisfy the Iterator trait bound in order to use Rayon here?

I'm attempting to parallelise the Ramer–Douglas-Peucker line simplification algorithm by using Rayon's par_iter instead of iter:
extern crate num_traits;
use num_traits::{Float, ToPrimitive};
extern crate rayon;
use self::rayon::prelude::*;
#[derive(PartialEq, Clone, Copy, Debug)]
pub struct Coordinate<T>
where T: Float
{
pub x: T,
pub y: T,
}
#[derive(PartialEq, Clone, Copy, Debug)]
pub struct Point<T>(pub Coordinate<T>) where T: Float;
impl<T> Point<T>
where T: Float + ToPrimitive
{
pub fn new(x: T, y: T) -> Point<T> {
Point(Coordinate { x: x, y: y })
}
pub fn x(&self) -> T {
self.0.x
}
pub fn y(&self) -> T {
self.0.y
}
}
unsafe impl<T> Send for Point<T> where T: Float {}
unsafe impl<T> Sync for Point<T> where T: Float {}
fn distance<T>(a: &Point<T>, p: &Point<T>) -> T
where T: Float
{
let (dx, dy) = (a.x() - p.x(), a.y() - p.y());
dx.hypot(dy)
}
// perpendicular distance from a point to a line
fn point_line_distance<T>(point: &Point<T>, start: &Point<T>, end: &Point<T>) -> T
where T: Float
{
if start == end {
distance(point, start)
} else {
let numerator = ((end.x() - start.x()) * (start.y() - point.y()) -
(start.x() - point.x()) * (end.y() - start.y()))
.abs();
let denominator = distance(start, end);
numerator / denominator
}
}
// Ramer–Douglas-Peucker line simplification algorithm
fn rdp<T>(points: &[Point<T>], epsilon: &T) -> Vec<Point<T>>
where T: Float + Send + Sync
{
if points.is_empty() {
return points.to_vec();
}
let mut dmax = T::zero();
let mut index: usize = 0;
let mut distance: T;
for (i, _) in points.par_iter().enumerate().take(points.len() - 1).skip(1) {
distance = point_line_distance(&points[i], &points[0], &*points.last().unwrap());
if distance > dmax {
index = i;
dmax = distance;
}
}
if dmax > *epsilon {
let mut intermediate = rdp(&points[..index + 1], &*epsilon);
intermediate.pop();
intermediate.extend_from_slice(&rdp(&points[index..], &*epsilon));
intermediate
} else {
vec![*points.first().unwrap(), *points.last().unwrap()]
}
}
#[cfg(test)]
mod test {
use super::{Point};
use super::{rdp};
#[test]
fn rdp_test() {
let mut vec = Vec::new();
vec.push(Point::new(0.0, 0.0));
vec.push(Point::new(5.0, 4.0));
vec.push(Point::new(11.0, 5.5));
vec.push(Point::new(17.3, 3.2));
vec.push(Point::new(27.8, 0.1));
let mut compare = Vec::new();
compare.push(Point::new(0.0, 0.0));
compare.push(Point::new(5.0, 4.0));
compare.push(Point::new(11.0, 5.5));
compare.push(Point::new(27.8, 0.1));
let simplified = rdp(&vec, &1.0);
assert_eq!(simplified, compare);
}
}
I've impld Send and Sync for Point<T>, but when I switch to par_iter, I get the following error:
error[E0277]: the trait bound rayon::par_iter::skip::Skip<rayon::par_iter::take::Take<rayon::par_iter::enumerate::Enumerate<rayon::par_iter::slice::SliceIter<'_, Point<T>>>>>: std::iter::Iterator is not satisfied
--> lib.rs:107:5
= note: rayon::par_iter::skip::Skip<rayon::par_iter::take::Take<rayon::par_iter::enumerate::Enumerate<rayon::par_iter::slice::SliceIter<'_, Point<T>>>>> is not an iterator; maybe try calling .iter() or a similar method
= note: required by std::iter::IntoIterator::into_iter
I don't understand what it's asking for. Is the problem that I'm operating on a tuple?
Rayon's parallel iterators implement ParallelIterator, not Iterator. In particular, this means you cannot just put a par_iter() in a for-loop header and expect it to suddenly be parallel. for is sequential.
Since your original code isn't written in terms of iterator functions, but rather as for loops, you can't parallelize it simply with the switch to par_iter(), but have to actually redesign the code.
In particular, the failing part of the code seems to be implementing the max_by_key function.

Resources