arifd
October 7, 2020, 11:16am
1
Hi, I'm trying to use the newtype pattern to avoid bugs and be a better programmer
I have struct Foo(usize)
I want to be able to %, /, >=, +, - all with other usizes.
Add and Sub, Div and Rem have been easy: (example)
impl std::ops::Add<usize> for Foo {
type Output = Self;
fn add(self, other: usize) -> Self::Output {
Self(self.0 + other)
}
}
But I'm really struggling with the compare operators. they require a number of previous Traits to also be implemented, and I'm getting lost in the woods a little.
Please help!
Could you show us your Foo structure?
arifd
October 7, 2020, 11:28am
3
it is a tuple struct, litterally that: struct Foo(usize)
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
struct Foo(usize);
impl std::ops::Add<Foo> for Foo {
type Output = Self;
fn add(self, other: Foo) -> Self::Output {
Self(self.0 + other.0)
}
}
impl std::ops::Add<usize> for Foo {
type Output = Self;
fn add(self, other: usize) -> Self::Output {
Self(self.0 + other)
}
}
impl std::fmt::Display for Foo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
fn main() {
let foo1 = Foo(0);
let foo2 = Foo(15);
let foo3 = Foo(20);
let foo4 = Foo(0);
println!("{}, {}, {}", foo1 == foo2, foo2 == foo3, foo1 == foo4);
println!("{}", foo1 + foo2 + foo3);
println!("{}", foo1 < foo2);
println!("{}", foo1 + 10usize);
}
arifd
October 7, 2020, 11:37am
5
Thank you very much, but I want to compare it with a usize, not with another of itself... i.e. if foo > 14 {...}
So you can't use #[derive] in this case as all the ops need to be <usize> for Foo
We can only compare equal types. However, thanks to rust's zero-cost abstractions, you can implement this:
impl From<usize> for Foo {
fn from(value: usize) -> Self {
Self(value)
}
}
Then, call .into() on the usize value:
println!("{}", foo2 < (10usize).into())
arifd
October 7, 2020, 11:45am
7
Ah really!? But Foo is a usize!
What about something like impl Deref for Foo?
impl std::ops::Deref for Foo {
type Target = usize;
fn deref(&self) -> &Self::Target {
&self.0
}
}
Then, do this:
println!("{}", *foo2 < 10usize)
Good idea. Looks much cleaner
arifd
October 7, 2020, 11:49am
9
Ah nice, so then, I don't need to impl any maths operations at all now, right. It's now just a straight up usize
Well, not only :
use core::cmp::Ordering;
use std::cmp::{PartialOrd, PartialEq};
struct Foo(usize);
impl PartialEq<usize> for Foo {
fn eq(&self, other: &usize) -> bool {
self.0.eq(other)
}
}
impl PartialOrd<usize> for Foo {
fn partial_cmp(&self, other: &usize) -> Option<Ordering> {
self.0.partial_cmp(other)
}
}
fn main() {
assert!(Foo(1) > 0);
}
arifd
October 7, 2020, 1:15pm
12
Wow, snap!
I just concurrently figured it out myself!
use std::cmp::{PartialOrd, Ordering};
struct Foo(usize);
impl PartialEq<usize> for Foo {
fn eq(&self, other: &usize) -> bool {
self.0 == *other
}
}
impl PartialOrd<usize> for Foo {
fn partial_cmp(&self, other: &usize) -> Option<Ordering> {
Some(self.0.cmp(&other))
}
}
fn main() {
dbg!(Foo(0) < 1usize);
}
Freaky
I didn't see that in the docs for PartialOrd in the code section (PartialOrd in std::cmp - Rust ). They should make it more explicit in the examples. Good to know something new!