How to make this Rust code less redundant? - rust

How would you design this better in Rust? More Specifically is there a way to collapse the redundancy down using traits or enums?
Background: I have a C++ / Python background and this is my first attempt to see how the language actually flows after reading the Rust book. Not having class inheritance is something I don't really know how to design around yet.
trait TemperatureConversion {
// https://www.nist.gov/pml/weights-and-measures/si-units-temperature
fn to_celcius(&self) -> f64;
fn to_fahrenheit(&self) -> f64;
fn to_kelvin(&self) -> f64;
}
struct Celcius {
value: f64,
}
struct Fahrenheit {
value: f64,
}
struct Kelvin {
value: f64,
}
impl Celcius {
fn new(value: f64) -> Celcius {
Celcius { value }
}
}
impl Fahrenheit {
fn new(value: f64) -> Fahrenheit {
Fahrenheit { value }
}
}
impl Kelvin {
fn new(value: f64) -> Kelvin {
Kelvin { value }
}
}
impl TemperatureConversion for Celcius {
fn to_celcius(&self) -> f64 {
self.value
}
fn to_fahrenheit(&self) -> f64 {
(self.value * 1.8) + 32.0
}
fn to_kelvin(&self) -> f64 {
self.value + 273.15
}
}
impl TemperatureConversion for Fahrenheit {
fn to_celcius(&self) -> f64 {
(self.value - 32.0) / 1.8
}
fn to_fahrenheit(&self) -> f64 {
self.value
}
fn to_kelvin(&self) -> f64 {
(self.value - 32.0) / 1.8 + 273.15
}
}
impl TemperatureConversion for Kelvin {
fn to_celcius(&self) -> f64 {
self.value - 273.15
}
fn to_fahrenheit(&self) -> f64 {
(self.value - 273.15) * 1.8 + 32.0
}
fn to_kelvin(&self) -> f64 {
self.value
}
}
fn main() {
let c = Celcius::new(100.0);
println!("100C = {:.2}F or {:.2}K", c.to_fahrenheit(), c.to_kelvin());
let f = Fahrenheit::new(100.0);
println!("100F = {:.2}C or {:.2}K", f.to_celcius(), f.to_kelvin());
let k = Kelvin::new(100.0);
println!("100K = {:.2}C or {:.2}F", k.to_celcius(), k.to_fahrenheit());
}
edit: I believe this is the fix:
struct KelvinTemperature {
kelvin: f64,
}
impl KelvinTemperature {
fn new(kelvin: f64) -> KelvinTemperature {
KelvinTemperature { kelvin }
}
fn from_celcius(value: f64) -> KelvinTemperature {
KelvinTemperature {
kelvin: value + 273.15,
}
}
fn from_fahrenheit(value: f64) -> KelvinTemperature {
KelvinTemperature {
kelvin: (value - 32.0) / 1.8 + 273.15,
}
}
fn to_celcius(&self) -> f64 {
self.kelvin - 273.15
}
fn to_fahrenheit(&self) -> f64 {
(self.kelvin - 273.15) * 1.8 + 32.0
}
fn to_kelvin(&self) -> f64 {
self.kelvin
}
}
fn main() {
let temperature = KelvinTemperature::from_celcius(100.0);
println!(
"{:.2}C = {:.2}F = {:.2}K",
temperature.to_celcius(),
temperature.to_fahrenheit(),
temperature.to_kelvin()
);
}

The best design would likely be to avoid re-inventing the wheel and realize someone else has already done this better than we ever will in the time available. The uom (Units Of Measurement) crate provides units for almost every unit you can think of as well as every combination of them (Even composite units like K*ft^2/sec). However that does not make for a very helpful explanation so lets just ignore it for now.
The first issue I see with this code is that it isn't very easy to expand. If you want to add a new temperature you need to add to the TemperatureConversion trait and implement a bunch of functions for all of your conversion rates. The first change I would make would be to turn Temperature into a trait so it is easier to work with.
pub trait Temperature: Copy {
fn to_kelvin(self) -> f64;
fn from_kelvin(x: f64) -> Self;
/// Convert self to a different unit of temperature
fn convert<T: Temperature>(self) -> T {
T::from_kelvin(self.to_kelvin())
}
}
This also gives us the benefit of letting us use it to constrain type parameters later.
pub fn calculate_stuff<T: Temperature>(a: T, b: T) -> T;
Next, since we know that temperatures will all be implemented in the same way and there might be a bunch of them, it may be easier to make a macro for them.
macro_rules! define_temperature {
($name:ident, $kelvin_at_zero:literal, $kelvin_per_unit:literal) => {
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub struct $name(f64);
impl Temperature for $name {
fn to_kelvin(self) -> f64 {
self.0 * $kelvin_per_unit + $kelvin_at_zero
}
fn from_kelvin(x: f64) -> Self {
Self((x - $kelvin_at_zero) / $kelvin_per_unit)
}
}
};
}
define_temperature! {Kelvin, 1.0, 1.0}
define_temperature! {Celsius, 273.1, 1.0}
define_temperature! {Fahrenheit, 255.3722, 0.5555}
The macro makes it easy to implement a bunch of different units based on their conversion rates, but the trait is not too restrictive so we could potentially implement units that do not follow a linear scale.

Related

Design patterns without the box

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

How to override trait implementations for specific types in Rust?

In the below example implementing either one of those traits would work. Compiler doesn't let to override the implementation for specific types.
Is there any other way to achieve this?
trait Giver<T,U> {
fn give_first(&self) -> T;
fn give_second(&self) -> U;
}
struct Pair<T,U>(T,U);
impl<T,U> Giver<T,U> for Pair<T,U> where T:Clone, U:Clone {
fn give_first(&self) -> T {
(self.0).clone()
}
fn give_second(&self) -> U {
(self.1).clone()
}
}
//works
impl Giver<i32, i32> for Pair<f32, f32> {
fn give_first(&self) -> i32 {
1
}
fn give_second(&self) -> i32 {
2
}
}
//error[E0119]: conflicting implementations of trait `Giver<f32, f32>` for type `Pair<f32, f32>`:
impl Giver<f32, f32> for Pair<f32, f32> {
fn give_first(&self) -> f32 {
1.0
}
fn give_second(&self) -> f32 {
2.0
}
}
#![feature(specialization)]
trait Giver<T,U> {
fn give_first(&self) -> T;
fn give_second(&self) -> U;
}
#[derive(Debug)]
struct Pair<T,U>(T,U);
impl<T,U> Giver<T,U> for Pair<T,U> where T:Clone, U:Clone {
default fn give_first(&self) -> T {
(self.0).clone()
}
default fn give_second(&self) -> U {
(self.1).clone()
}
}
impl Giver<i32, i32> for Pair<f32, f32> {
fn give_first(&self) -> i32 {
1
}
fn give_second(&self) -> i32 {
2
}
}
impl Giver<f32, f32> for Pair<f32, f32> {
fn give_first(&self) -> f32 {
3.0
}
fn give_second(&self) -> f32 {
4.0
}
}
fn main() {
{
let p = Pair(0.0, 0.0);
let first: i32 = p.give_first();
let second: i32 = p.give_second();
println!("{}, {}", first, second); // 1, 2
}
{
let p: Pair<f32, f32> = Pair(0.0, 0.0);
let first: f32 = p.give_first();
let second: f32 = p.give_second();
println!("{}, {}", first, second); // 3, 4
}
}
rust nightly seems to support it if we explicitly specify the expected return type. I tried it with #![feature(specialization)]. There might be a more elegant solution (would wait for someone with more knowledge on this).
output: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=d618cd9b534a5ba49199a2efdcf607bd

Rust: impl From<_> for usize, u64, u32, etc

Let S be a struct. I want to implement From for all uint types. Is there a terse way to do this?
E.g., I want to write this code
impl From<S> for usize {
fn from(s: S) -> usize {
s.field_a + s.field_b
}
}
impl From<S> for u64 {
fn from(s: S) -> u64 {
s.field_a + s.field_b
}
}
impl From<S> for u32 {
fn from(s: S) -> u32 {
s.field_a + s.field_b
}
}
...
as
impl From<S> for uint {
fn from(s: S) -> uint {
s.field_a + s.field_b
}
}
My first thought was to use traits to group all the uints a la how we pass traits as parameters. Here's my attempt:
use std::ops::Add;
impl From<S> for impl From + Add {
fn from<T: From + Add>(s: S) -> T {
T::from(s.field_a) + T::from(s.field_b)
}
}
But this doesn't work and feels janky (uints aren't just things that impl From and Add).
Don't know where to go from here! Any help would be appreciated!
A macro could work. (playground)
struct S {
field_a: u8,
field_b: u8,
}
macro_rules! impl_from_s {
($($uint_type: ty),*) => {
$(
impl From<S> for $uint_type {
fn from(s: S) -> $uint_type {
<$uint_type>::from(s.field_a) + <$uint_type>::from(s.field_b)
}
}
)*
}
}
impl_from_s!(u8, u16, u32, u64, u128);

How can I convert a f64 to f32 and get the closest approximation and the next greater or smaller value?

Possible pseudocode for the operation could be:
fn f32_greater(x: f64) -> f32 {
let mut y = x as f32; //I get closest
while f64::from(y) < x {
y = nextafter(y, f32::INFINITY);
}
y
}
fn f32_smaller(x: f64) -> f32 {
let mut y = x as f32; //I get closest
while f64::from(y) > x {
y = nextafter(y, f32::NEG_INFINITY);
}
y
}
I can not find an equivalent to C11's nextafter function in the libc crate or in the methods on f64
For context, I have an R-tree index using f32. I want to search the region with coordinates provided as a f64, so I need the smallest possible region in f32 that includes the f64 value.
This function was removed from the standard library. A solution could be to use the float_extras crate, but I don't really like the way of this crate so here my solution:
mod float {
use libc::{c_double, c_float};
use std::{f32, f64};
#[link_name = "m"]
extern "C" {
pub fn nextafter(x: c_double, y: c_double) -> c_double;
pub fn nextafterf(x: c_float, y: c_float) -> c_float;
// long double nextafterl(long double x, long double y);
// double nexttoward(double x, long double y);
// float nexttowardf(float x, long double y);
// long double nexttowardl(long double x, long double y);
}
pub trait NextAfter {
fn next_after(self, y: Self) -> Self;
}
impl NextAfter for f32 {
fn next_after(self, y: Self) -> Self {
unsafe { nextafterf(self, y) }
}
}
impl NextAfter for f64 {
fn next_after(self, y: Self) -> Self {
unsafe { nextafter(self, y) }
}
}
pub trait Succ {
fn succ(self) -> Self;
}
impl Succ for f32 {
fn succ(self) -> Self {
self.next_after(f32::INFINITY)
}
}
impl Succ for f64 {
fn succ(self) -> Self {
self.next_after(f64::INFINITY)
}
}
pub trait Pred {
fn pred(self) -> Self;
}
impl Pred for f32 {
fn pred(self) -> Self {
self.next_after(f32::NEG_INFINITY)
}
}
impl Pred for f64 {
fn pred(self) -> Self {
self.next_after(f64::NEG_INFINITY)
}
}
}
use crate::float::{Pred, Succ};
use num_traits::cast::{FromPrimitive, ToPrimitive};
fn f32_greater<T>(x: T) -> Option<f32>
where
T: ToPrimitive + FromPrimitive + std::cmp::PartialOrd,
{
let mut y = x.to_f32()?;
while T::from_f32(y)? < x {
y = y.succ();
}
Some(y)
}
fn f32_smaller<T>(x: T) -> Option<f32>
where
T: ToPrimitive + FromPrimitive + std::cmp::PartialOrd,
{
let mut y = x.to_f32()?;
while T::from_f32(y)? > x {
y = y.pred();
}
Some(y)
}
fn main() {
let a = 42.4242424242424242;
println!(
"{:.16?} < {:.16} < {:.16?}",
f32_smaller(a),
a,
f32_greater(a)
);
}
I don't understand why they don't include it in the num crate.

Require trait implementations to be convertible between each other

I'm trying to have a Rust trait X which requires that anyone implementing X can convert to other implementations of X.
So I'm trying to make the declaration of X enforce this like so:
trait X<T> : From<T> where T: X {}
But the compiler is telling me that it doesn't find any type arguments in my specification of T, because T: X needs some type information T: X<...>. But this way there will always be one type argument too little; e.g.
trait X<T, U> : From<T> where T: X<U> {}
Can I get around this in some way? Doing where T: X<_> is not allowed.
Rather than trying to restrict the implementers, I think it would be simpler to provide the implementation as part of the trait:
trait Length {
fn unit_in_meters() -> f64;
fn value(&self) -> f64;
fn new(value: f64) -> Self;
fn convert_to<T:Length>(&self) -> T {
T::new(self.value() * Self::unit_in_meters() / T::unit_in_meters())
}
}
struct Mm {
v: f64,
}
impl Length for Mm {
fn unit_in_meters() -> f64 { 0.001 }
fn value(&self) -> f64 { self.v }
fn new(value: f64) -> Mm {
Mm{ v: value }
}
}
struct Inch {
v: f64,
}
impl Length for Inch {
fn unit_in_meters() -> f64 { 0.0254 }
fn value(&self) -> f64 { self.v }
fn new(value: f64) -> Inch {
Inch{ v: value }
}
}
fn main() {
let foot = Inch::new(12f64);
let foot_in_mm: Mm = foot.convert_to();
println!("One foot in mm: {}", foot_in_mm.value());
}
Play link
For fun, with the associated_consts feature you can swap the method for a constant conversion factor.
#![feature(associated_consts)]
trait Length {
const UNIT_IN_METERS: f64;
fn value(&self) -> f64;
fn new(value: f64) -> Self;
fn convert_to<T:Length>(&self) -> T {
T::new(self.value() * Self::UNIT_IN_METERS / T::UNIT_IN_METERS)
}
}
struct Mm {
v: f64,
}
impl Length for Mm {
const UNIT_IN_METERS: f64 = 0.001;
fn value(&self) -> f64 { self.v }
fn new(value: f64) -> Mm {
Mm{ v: value }
}
}
struct Inch {
v: f64,
}
impl Length for Inch {
const UNIT_IN_METERS: f64 = 0.0254;
fn value(&self) -> f64 { self.v }
fn new(value: f64) -> Inch {
Inch{ v: value }
}
}
fn main() {
let foot = Inch::new(12f64);
let foot_in_mm: Mm = foot.convert_to();
println!("One foot in mm: {}", foot_in_mm.value());
}
Play link

Resources