I just finished a Rosetta Code task translation from Crystal to Rust and thought I’d share some observations.
-
Even for this simple example, Rust caused me all kinds of tears and pain to learn how to do string->number and number->string conversions. Crystal|Ruby is so much simpler. (See: https://users.rust-lang.org/t/number-string-back-to-string-reverse-number/51913/15)
-
Rust is a bit faster, on an apples-to-apples comparison doing this algorithm.
-
Rust raw executable is larger, but stripped is almost half the size.
-
Conclusion: If you really, Really, REALLY need optimal speed, use Rust. If you want to get stuff done quickly, and still have time to live your life, use Crystal. YMMV!
Hear are some statistics:
| speed | exe (raw) bytes | exe (stripped) bytes
Crystal | 25.40s | 886,520 | 434,600
Rust | 20.06s | 1,373,528 | 268,576
Compiled as:
Crystal:
$ crystal build --release palindromicgapfuls.cr
Rust:
$ rustc -C opt-level=3 -C target-cpu=native -C codegen-units=1 -C lto palindromicgapuls.rs
Here’s the Crystal code:
def palindromicgapfuls(digit, count, keep)
skipped = 0 # initial count of skipped values
to_skip = count - keep # count of unwanted values to skip
gapfuls = [] of UInt64 # array of palindromic gapfuls
nn = digit * 11 # digit gapful divisor: 11, 22,...88, 99
(2..).select do |power|
base = 10_u64**(power >> 1) # value of middle digit position: 10..
base11 = base * 11 # value of middle two digits positions: 110..
this_lo = base * digit # starting half for this digit: 10.. to 90..
next_lo = base * (digit + 1) # starting half for next digit: 20.. to 100..
this_lo.step(to: next_lo - 1, by: 10) do |front_half| # d_00; d_10; d_20; ...
palindrome, left_half = 0_u64, front_half.to_s
palindrome = (left_half + left_half.reverse).to_u64 if power.odd?
palindrome = (left_half.rchop + left_half.reverse).to_u64 if power.even?
basep = power.odd? ? base11 : base
10.times do
(gapfuls << palindrome if (skipped += 1) > to_skip) if palindrome.divisible_by?(nn)
palindrome += basep
end
return gapfuls[0...keep] unless gapfuls.size < keep
end
end
end
start = Time.monotonic
count, keep = 20, 20
puts "First 20 palindromic gapful numbers ending with:"
1.upto(9) { |digit| puts "#{digit} : #{palindromicgapfuls(digit, count, keep)}" }
count, keep = 100, 15
puts "\nLast 15 of first 100 palindromic gapful numbers ending in:"
1.upto(9) { |digit| puts "#{digit} : #{palindromicgapfuls(digit, count, keep)}" }
count, keep = 1_000, 10
puts "\nLast 10 of first 1000 palindromic gapful numbers ending in:"
1.upto(9) { |digit| puts "#{digit} : #{palindromicgapfuls(digit, count, keep)}" }
count, keep = 100_000, 1
puts "\n100,000th palindromic gapful number ending with:"
1.upto(9) { |digit| puts "#{digit} : #{palindromicgapfuls(digit, count, keep)}" }
count, keep = 1_000_000, 1
puts "\n1,000,000th palindromic gapful number ending with:"
1.upto(9) { |digit| puts "#{digit} : #{palindromicgapfuls(digit, count, keep)}" }
count, keep = 10_000_000, 1
puts "\n10,000,000th palindromic gapful number ending with:"
1.upto(9) { |digit| puts "#{digit} : #{palindromicgapfuls(digit, count, keep)}" }
puts (Time.monotonic - start).total_seconds
Here’s the Rust code:
fn palindromicgapfuls(digit: u64, count: u64, keep: usize) -> Vec<u64> {
let mut skipped = 0u64; // initial count of skipped values
let to_skip = count - keep as u64; // count of unwanted values to skip
let mut gapfuls: Vec<u64> = vec![]; // array of palindromic gapfuls
let nn = digit * 11; // digit gapful divisor: 11, 22,...88, 99
let (mut power, mut base) = (1, 1u64);
loop { power += 1;
if power & 1 == 0 { base *= 10 }; // value of middle digit position: 10..
let base11 = base * 11; // value of middle two digits positions: 110..
let this_lo = base * digit; // starting half for this digit: 10.. to 90..
let next_lo = base * (digit + 1); // starting half for next digit: 20.. to 100..
for front_half in (this_lo..next_lo).step_by(10) { // d_00; d_10; d_20; ...
let (mut left_half, mut basep) = (front_half.to_string(), 0);
let right_half = left_half.chars().rev().collect::<String>();
if power & 1 == 1 { basep = base11; left_half.push_str(&right_half) }
else { basep = base; left_half.pop(); left_half.push_str(&right_half) };
let mut palindrome = left_half.parse::<u64>().unwrap();
for _ in 0..10 {
if palindrome % nn == 0 { skipped += 1; if skipped > to_skip { gapfuls.push(palindrome) } };
palindrome += basep;
}
if gapfuls.len() >= keep { return gapfuls[0..keep].to_vec() };
}
}
}
fn main() {
let t = std::time::Instant::now();
let (count, keep) = (20, 20);
println!("First 20 palindromic gapful numbers ending with:");
for digit in 1..10 { println!("{} : {:?}", digit, palindromicgapfuls(digit, count, keep)); }
let (count, keep) = (100, 15);
println!("\nLast 15 of first 100 palindromic gapful numbers ending in:");
for digit in 1..10 { println!("{} : {:?}", digit, palindromicgapfuls(digit, count, keep)); }
let (count, keep) = (1_000, 10);
println!("\nLast 10 of first 1000 palindromic gapful numbers ending in:");
for digit in 1..10 { println!("{} : {:?}", digit, palindromicgapfuls(digit, count, keep)); }
let (count, keep) = (100_000, 1);
println!("\n100,000th palindromic gapful number ending with:");
for digit in 1..10 { println!("{} : {:?}", digit, palindromicgapfuls(digit, count, keep)); }
let (count, keep) = (1_000_000, 1);
println!("\n1,000,000th palindromic gapful number ending with:");
for digit in 1..10 { println!("{} : {:?}", digit, palindromicgapfuls(digit, count, keep)); }
let (count, keep) = (10_000_000, 1);
println!("\n10,000,000th palindromic gapful number ending with:");
for digit in 1..10 { println!("{} : {:?}", digit, palindromicgapfuls(digit, count, keep)); }
println!("{:?}", t.elapsed())
}
Here is the full code and output on Rosettacode.org
Crystal:
Rust: