ackermann

Full source code

C++

// -*- mode: c++ -*-
// $Id$
// http://www.bagley.org/~doug/shootout/

#include <iostream>
#include <stdlib.h>

using namespace std;

int Ack(int M, int N) { return(M ? (Ack(M-1,N ? Ack(M,(N-1)) : 1)) : N+1); }

int main(int argc, char *argv[]) {
#ifdef SMALL_PROBLEM_SIZE
#define LENGTH 11
#else
#define LENGTH 12
#endif
    int n = ((argc == 2) ? atoi(argv[1]) : LENGTH);

    cout << "Ack(3," << n << "): " << Ack(3, n) << endl;
    return(0);
}

Rust

// Adapted from https://github.com/llvm/llvm-test-suite and
// http://www.bagley.org/~doug/shootout/
use std::env;

#[cfg(feature = "small_problem_size")]
const LENGTH: i32 = 11;

#[cfg(not(feature = "small_problem_size"))]
const LENGTH: i32 = 12;

fn ack(m: i32, n: i32) -> i32 {
    if m != 0 { ack(m - 1, if n != 0 { ack(m, n - 1) } else { 1 }) } else { n + 1 }
}

fn main() {
    let mut args = env::args();
    let n = if args.len() == 2 {
        args.nth(1).unwrap().parse::<i32>().unwrap()
    } else {
        LENGTH
    };
    println!("Ack(3,{}): {}", n, ack(3, n));
}

Porting notes

Conditionally declaring constants

C++

#ifdef SMALL_PROBLEM_SIZE
#define LENGTH 11
#else
#define LENGTH 12
#endif

Rust

#![allow(unused)]
fn main() {
#[cfg(feature = "small_problem_size")]
const LENGTH: i32 = 11;

#[cfg(not(feature = "small_problem_size"))]
const LENGTH: i32 = 12;
}

It's idiomatic to use const variables along with the cfg attributes to conditionally declare constants, as opposed to using macros in C/C++.

Declaring a function

C++

int Ack(int M, int N) { return(M ? (Ack(M-1,N ? Ack(M,(N-1)) : 1)) : N+1); }

Rust

#![allow(unused)]
fn main() {
fn ack(m: i32, n: i32) -> i32 {
    if m != 0 { ack(m - 1, if n != 0 { ack(m, n - 1) } else { 1 }) } else { n + 1 }
}
}

Declaring a function in Rust is not too dissimilar to C++ with some differences:

  • Functions are declared with the fn keyword.
  • The parameter type comes after the parameter name and a colon.
  • The return type comes after the parameter list and the ->.
  • The if statement condition expression doesn't need parentheses around them.
  • Rust has if expressions in addition to if statements (or the last statement in the blocks can be an expression), and lacks the ternary operator.
  • Rust has no int type and uses integer types with explicit widths such as i32.

Accessing program arguments

C++

int main(int argc, char *argv[]) {
    int n = ((argc == 2) ? atoi(argv[1]) : LENGTH);
    ...

Rust

fn main() {
    let mut args = env::args();
    let n = if args.len() == 2 {
        args.nth(1).unwrap().parse::<i32>().unwrap()
    } else {
        LENGTH
    };
    ...

Rust's main function doesn't have the argc and argv parameters. Instead the program arguments are accessed with the std::env API.

The std::option type is commonly used in Rust to represent the optional result of an operation. The unwrap function is used to access the result when it is assumed that it contains a valid result, which would cause a panic if it does not. For example, accessing the nth element of an argument array and parsing a string into an integer.

This common pattern on the program arguments are used in most of the programs here. I believe it achieves dual purposes, to allow the problem size changed and to makes it harder for the compiler to compile-time evaluate (constant-fold) the entire program logic.

Printing to standard output

C++

    cout << "Ack(3," << n << "): " << Ack(3, n) << endl;

Rust

#![allow(unused)]
fn main() {
    println!("Ack(3,{}): {}", n, ack(3, n));
}

println! is the standard way to print to standard output in Rust. The ! at the end indicates it's a macro. I won't go into macros here.

To print without the new line, we'd use print! instead.