The following two functions generate very different resulting assembly language:
pub struct X {
a: u64,
b: u64,
c: u64,
d: u64,
e: u64,
f: u64,
}
pub fn f(a: u8, x: X) -> u64 {
[
(0b000001, x.a),
(0b000010, x.b),
(0b000100, x.c),
(0b001000, x.d),
(0b010000, x.e),
(0b100000, x.f),
]
.into_iter()
.find(|(bb, _)| (*bb & a) != 0)
.map_or(0, |(_, m)| m)
}
pub fn g(a: u8, x: X) -> u64 {
match a {
_ if (a & 0b000001) != 0 => x.a,
_ if (a & 0b000010) != 0 => x.b,
_ if (a & 0b000100) != 0 => x.c,
_ if (a & 0b001000) != 0 => x.d,
_ if (a & 0b010000) != 0 => x.e,
_ if (a & 0b100000) != 0 => x.f,
_ => 0,
}
}
They do the same thing: based on a bit pattern, return the proper value. I prefer f because it separates the data and the logic, but it results in inferior assembly. Because I'm running simulations, a little is a lot. (see assembly with above playground link, generate release asm)
In f, Rust unnecessarily builds the array in memory instead of recognizing that the values are used and immediately discarded. g mashes the data and logic together, but Rust simply does the comparisons then returns the results, as you would expect.
Is there anything I can do to help this iterator-style code generate better code or am I better off writing imperative style?
There's also the slightly shorter
pub fn h(a: u8, x: X) -> u64 {
match a.trailing_zeros() {
0 => x.a,
1 => x.b,
2 => x.c,
3 => x.d,
4 => x.e,
5 => x.f,
_ => 0
}
}
Not sure how its assembly looks.
Related
I am trying to do a simple quadratic function that would return number of roots and their values via an enum:
enum QuadraticResult {
None,
OneRoot(f32),
TwoRoots(f32, f32),
}
fn solveQuadratic(a: f32, b: f32, c: f32) -> QuadraticResult {
let delta = b * b - 4.0 * a * c;
match delta {
< 0 => return QuadraticResult::None,
> 0 => return QuadraticResult::TwoRoots(0.0, 1.0),
_ => return QuadraticResult::OneRoot(0.0),
}
}
This doesn't compile as it complains about '<' and '>'. Is there a way to achieve this with match or do I need to use if
You can use a match guard, but that feels more verbose than a plain if statement:
return match delta {
d if d < 0 => QuadraticResult::None,
d if d > 0 => QuadraticResult::TwoRoots(0.0, 1.0),
_ => QuadraticResult::OneRoot(0.0),
}
If you want to handle the three cases where some value is greater than, equal to or less than another, you can match on an Ordering, which you can obtain by calling cmp (from the Ord trait) or partial_cmp (from the PartialOrd trait).
fn solve_quadratic(a: f32, b: f32, c: f32) -> QuadraticResult {
let delta = b * b - 4.0 * a * c;
match delta.partial_cmp(&0.0).expect("I don't like NaNs") {
Ordering::Less => QuadraticResult::None,
Ordering::Greater => QuadraticResult::TwoRoots(0.0, 1.0),
Ordering::Equal => QuadraticResult::OneRoot(0.0),
}
}
You can, but you'll want to create a variable binding when you do it and turn it into an actual expression:
match delta {
d if d < 0.0 => QuadraticResult::None,
d if d > 0.0 => QuadraticResult::TwoRoots(0.0, 1.0),
_ => QuadraticResult::OneRoot(0.0),
}
I'm not sure this is any better than just splitting this into an if statement though.
Warning: as of rustc 1.67.0 (fc594f156 2023-01-24) this method still works, but will stop working for float literals in a future release.
You can use a Range Pattern which are supported in match expressions. This requires the exclusive_range_pattern feature.
#![feature(exclusive_range_pattern)]
fn solveQuadratic(a: f32, b: f32, c: f32) -> QuadraticResult {
let delta = b * b - 4.0 * a * c;
match delta {
std::f32::MIN..0.0 => return QuadraticResult::None,
0.0 => return QuadraticResult::OneRoot(0.0),
_ => return QuadraticResult::TwoRoots(0.0, 1.0),
}
}
Playground
expected type parameter T, found type parameter A error display. I have written lifetime implementation code also but it stills doesn't solve the problem. What's wrong I am doing?
fn main() {
let x = 3;
let y = 5.0;
let max_value = max(x, y);
println!("The maximum value is {}", max_value);
}
fn max<T: PartialOrd, A: PartialOrd>(x: T, y: A) -> T {
if x > y {
x
} else {
y
}
}
// fn main() {
// let x = 3;
// let y = 5.0;
// let max_value = max(&x, &y);
// println!("The maximum value is {}", max_value);
// }
// fn max<'a, T: PartialOrd + Copy, A: PartialOrd + Copy>(x: &'a T, y: &'a A) -> &'a T {
// if x > y {
// x
// } else {
// y
// }
// }
T and A do not have to be the same type, so you have two problems.
The first is that you constrain T and A to be PartialOrd, which is the same thing as PartialOrd<Self>. So your actual constraints are T: PartialOrd<T>, A: PartialOrd<A>. This means you can compare the order of T's to other T's and A's to other A's, but x > y compares a T to an A.
Instead, you need to constrain T: PartialOrd<A>. (This also fails, but because of the invocation in main() -- more on that later.)
Second, the function is declared to return T but the else block returns y, which is not a T. Rust is statically typed, so it expects the types to exactly match.
This could be fixed by requiring that A can be converted to T (that is, A: Into<T>) and then you can return y.into() from the else block.
So at this point, we have:
fn main() {
let x = 3;
let y = 5.0;
let max_value = max(x, y);
println!("The maximum value is {}", max_value);
}
fn max<T: PartialOrd<A>, A: Into<T>>(x: T, y: A) -> T {
if x > y {
x
} else {
y.into()
}
}
But now you are left with more problems:
There are no types T and A satisfying T: PartialOrd<A> where T is an integer and A is a float, therefore you cannot call this function with 3 and 5.0 as you do in main().
Likewise, there's no implementation of Into<T> on A for an integer type T and a float type A.
x > y will move x and y, and then you cannot return them later. This is trivially fixed by constraining both T and A to be Copy.
The second issue could be fixed by having an enum that means "either T or A" and returning that instead. The either crate has such a type called Either, which we can use here as Either<T, A>:
use either::Either;
fn main() {
let x = 3;
let y = 5.0;
let max_value = max(x, y);
println!("The maximum value is {}", max_value);
}
fn max<T: PartialOrd<A> + Copy, A: Copy>(x: T, y: A) -> Either<T, A> {
if x > y {
Either::Left(x)
} else {
Either::Right(y)
}
}
(The println! works because Either<T, A> implements Display when both T and A do.)
You are still left with the problem where there's no built-in ordering implementation between integers and floats.
A "hail mary" solution could be to require that T and A can both be converted to f64 and then convert x and y to f64 before comparing them:
use either::Either;
fn main() {
let x = 3;
let y = 5.0;
let max_value = max(x, y);
println!("The maximum value is {}", max_value);
}
fn max<T: Copy + Into<f64>, A: Copy + Into<f64>>(x: T, y: A) -> Either<T, A> {
if x.into() > y.into() {
Either::Left(x)
} else {
Either::Right(y)
}
}
This is the first bit of code we have that actually compiles, and this might be good enough for your purposes. There are still some issues that remain, however:
i64 and u64 cannot be losslessy converted to f64, therefore they do not implement Into<f64>, and so if you change let x = 3; to let x = 3u64; (or 3i64) compilation will again fail.
f64 does not implement Ord because it's possible for there to be two f64 values x and y that are not equal but neither is greater than the other -- if either value is NaN, for example. This won't cause your program to crash, but it may produce an unexpected or incorrect result.
I suspect that this is a learning exercise, so hopefully this answer helps you understand what is wrong with the original code. I would not recommend a function like this in a real-world program; instead, it would be far better to convert both arguments to be of the same Ord-implementing type ahead of time and then you can use the built-in std::cmp::max function (or Ord::max).
I have the following code where I need to do direct comparisons between the ranks. For example I need to be able to do self as u8 + 1 == other as u8.
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
#[repr(u8)]
pub enum Rank {
Ace = 1,
Two,
Three,
Four,
Five,
Six,
Seven,
Eight,
Nine,
Ten,
Jack,
Queen,
King,
}
impl TryFrom<u8> for Rank {
type Error = ();
// TODO: replace with macro or find better option
fn try_from(v: u8) -> Result<Self, Self::Error> {
match v {
x if x == Rank::Ace as u8 => Ok(Rank::Ace),
x if x == Rank::Two as u8 => Ok(Rank::Two),
x if x == Rank::Three as u8 => Ok(Rank::Three),
x if x == Rank::Four as u8 => Ok(Rank::Four),
x if x == Rank::Five as u8 => Ok(Rank::Five),
x if x == Rank::Six as u8 => Ok(Rank::Six),
x if x == Rank::Seven as u8 => Ok(Rank::Seven),
x if x == Rank::Eight as u8 => Ok(Rank::Eight),
x if x == Rank::Nine as u8 => Ok(Rank::Nine),
x if x == Rank::Ten as u8 => Ok(Rank::Ten),
x if x == Rank::Jack as u8 => Ok(Rank::Jack),
x if x == Rank::Queen as u8 => Ok(Rank::Queen),
x if x == Rank::King as u8 => Ok(Rank::King),
_ => Err(()),
}
}
}
Is there a more efficient way to write this without using a macro and basically writing it all out anyway?.
tl;dr: Yes, there is a way to do this without macros, but it's unsafe. Macros are fine; use num_enum instead.
If you are willing to delve into the realm of unsafe code, you can use std::mem::transmute() to convert the u8 to Rank:
fn try_from(v: u8) -> Result<Self, Self::Error> {
match v {
x if x >= Rank::Ace as u8 && x <= Rank::King as u8 =>
Ok(unsafe { std::mem::transmute(x) }),
_ => Err(()),
}
}
Beware, if the enum values change later and x >= Rank::Ace as u8 && x <= Rank::King as u8 no longer guarantees that the value is a valid enum value, undefined behavior will result if a bad value is converted.
If you take this approach, I would put very obvious warning comments on the definition of Rank so that others (and future-you) know that changing the values without suitably updating the try_from implementation could cause UB.
From the std::mem::transmute() documentation:
transmute is incredibly unsafe. There are a vast number of ways to cause undefined behavior with this function. transmute should be the absolute last resort.
This is a trade-off of saving a mere 11-12 lines of code at the cost of potentially sabotaging yourself later. I'm giving this answer for the sake of completeness, to say "yes, there is a way to do what you ask, but you really shouldn't do it this way."
In Clojure, I use a function called iterate that:
Returns a lazy sequence of x, (f x), (f (f x)) etc. f must be free of side-effects
Is there something similar in Rust?
For example, I have this function that I'd like to pass to a function with a number and then iterate over a stream/range/vector until I find Some(1):
fn coltz(n: u64) -> Option<u64> {
match n % 2 {
0 => Some(n / 2),
_ => Some(3 * n + 1)
}
}
You can repeatedly call a closure using std::iter::repeat_with():
repeat_with(move || {
let result = x;
x = f(x);
result
})
The return value is an iterator over the successive return values of the closure.
We use move to move x into the closure, as the current state of our iteration. Inside the closure we update x with f(x) and return the old value (so in the first iteration we return the original x).
Here is a complete working example:
use std::iter::repeat_with;
fn collatz(n: u64) -> u64 {
match n % 2 {
0 => n / 2,
_ => 3 * n + 1,
}
}
fn iterate<F, X>(f: F, mut x: X) -> impl Iterator<Item = X>
where
F: Fn(X) -> X,
X: Copy,
{
repeat_with(move || {
let result = x;
x = f(x);
result
})
}
fn main() {
for i in iterate(collatz, 12).take_while(|&x| x != 1) {
println!("{}", i);
}
}
Playground
As of Rust 1.34, you can use iter::successors:
fn coltz(n: u64) -> Option<u64> {
match n % 2 {
0 => Some(n / 2),
_ => Some(3 * n + 1),
}
}
use std::iter;
fn main() {
let sequence = iter::successors(Some(10), |&v| coltz(v)).take_while(|&v| v != 1);
for v in sequence {
println!("{}", v);
}
}
12
6
3
10
5
16
8
4
2
I am trying to do a simple quadratic function that would return number of roots and their values via an enum:
enum QuadraticResult {
None,
OneRoot(f32),
TwoRoots(f32, f32),
}
fn solveQuadratic(a: f32, b: f32, c: f32) -> QuadraticResult {
let delta = b * b - 4.0 * a * c;
match delta {
< 0 => return QuadraticResult::None,
> 0 => return QuadraticResult::TwoRoots(0.0, 1.0),
_ => return QuadraticResult::OneRoot(0.0),
}
}
This doesn't compile as it complains about '<' and '>'. Is there a way to achieve this with match or do I need to use if
You can use a match guard, but that feels more verbose than a plain if statement:
return match delta {
d if d < 0 => QuadraticResult::None,
d if d > 0 => QuadraticResult::TwoRoots(0.0, 1.0),
_ => QuadraticResult::OneRoot(0.0),
}
If you want to handle the three cases where some value is greater than, equal to or less than another, you can match on an Ordering, which you can obtain by calling cmp (from the Ord trait) or partial_cmp (from the PartialOrd trait).
fn solve_quadratic(a: f32, b: f32, c: f32) -> QuadraticResult {
let delta = b * b - 4.0 * a * c;
match delta.partial_cmp(&0.0).expect("I don't like NaNs") {
Ordering::Less => QuadraticResult::None,
Ordering::Greater => QuadraticResult::TwoRoots(0.0, 1.0),
Ordering::Equal => QuadraticResult::OneRoot(0.0),
}
}
You can, but you'll want to create a variable binding when you do it and turn it into an actual expression:
match delta {
d if d < 0.0 => QuadraticResult::None,
d if d > 0.0 => QuadraticResult::TwoRoots(0.0, 1.0),
_ => QuadraticResult::OneRoot(0.0),
}
I'm not sure this is any better than just splitting this into an if statement though.
Warning: as of rustc 1.67.0 (fc594f156 2023-01-24) this method still works, but will stop working for float literals in a future release.
You can use a Range Pattern which are supported in match expressions. This requires the exclusive_range_pattern feature.
#![feature(exclusive_range_pattern)]
fn solveQuadratic(a: f32, b: f32, c: f32) -> QuadraticResult {
let delta = b * b - 4.0 * a * c;
match delta {
std::f32::MIN..0.0 => return QuadraticResult::None,
0.0 => return QuadraticResult::OneRoot(0.0),
_ => return QuadraticResult::TwoRoots(0.0, 1.0),
}
}
Playground