Hacker News new | ask | show | jobs
by stefano_c 1518 days ago
One possible solution in Rust could be:

    enum Value {
        Fizz,
        Buzz,
        FizzBuzz,
        Number(usize),
    }

    impl std::fmt::Display for Value {
        fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
            match self {
                Value::Fizz => write!(f, "Fizz"),
                Value::Buzz => write!(f, "Buzz"),
                Value::FizzBuzz => write!(f, "FizzBuzz"),
                Value::Number(num) => write!(f, "{}", num),
            }
        }
    }

    const fn get_fizzbuzz_equivalent(number: usize) -> Value {
        if number % 15 == 0 {
            Value::FizzBuzz
        } else if number % 3 == 0 {
            Value::Fizz
        } else if number % 5 == 0 {
            Value::Buzz
        } else {
            Value::Number(number)
        }
    }

    fn main() {
        (1..100).for_each(|num| println!("{}", get_fizzbuzz_equivalent(num)));
    }
1 comments

While this implements FizzBuzz it does not actually end up doing the work at compile time.

You annotate get_fizzbuzz_equivalent() with const, so Rust would evaluate that on constant inputs at compile time, but that's not very interesting since it's basically a switch.

The use of const here does not oblige Rust to somehow figure out everywhere you can use this function and do the work at compile time since the inputs might be (and are here) variables. Sure enough if you try in Godbolt you will see that eliding const makes no real difference.

Rust's const today is far less powerful than something like C++ constexpr, I suspect that you can't really do what Nim did in a reasonable way with Rust. You could I'm sure get there with build.rs and/or proc macros, but that's not really in the spirit of this exercise.

To elaborate, the parent doesn't call get_fizzbuzz_equivalent in a "const context", which would require it to be evaluated at compile time. So it's called at runtime like it didn't have `const`.

You can do something like the nim without build.rs or proc macros:

    #[derive(Debug, Copy, Clone)]
    enum Value {
        Fizz,
        Buzz,
        FizzBuzz,
        Number(usize),
    }
    
    const fn get_fizzbuzz_equivalent<const N: usize>() -> [Value; N] {
        let mut result = [Value::FizzBuzz; N];
    
        let mut i: usize = 0;
        while i < N {
            let n = i + 1;
            if n % 15 == 0 {
                result[i] = Value::FizzBuzz;
            } else if n % 3 == 0 {
                result[i] = Value::Fizz;
            } else if n % 5 == 0 {
                result[i] = Value::Buzz;
            } else {
                result[i] = Value::Number(n);
            };
    
            i += 1;
        }
    
        result
    }
    
    fn main() {
        const FIZZBUZZ: [Value; 31] = get_fizzbuzz_equivalent();
    
        println!("{:?}", FIZZBUZZ);
    }
    
There are certainly some ergonomic issues here; having to use while because for isn't usable in const contexts yet, which is annoying. But this does compute the whole array at compile time.

(Shout-out to https://stackoverflow.com/questions/67538438/const-array-fro... which I basically smashed together with OP's code to produce the above example.)

> having to use while because for isn't usable in const contexts yet

For people wondering why, both as Rust outsiders or Rust beginners:

Rust in some sense really only has one loop, just named "loop", which is an infinite loop like while(true) { } in various other languages. Other looping is just syntactic sugar for "loop" specifying some way to escape the loop. Rust's for loop is sugar for a "loop" that uses the IntoIterator trait to get an Iterator and then call next() on it each time the loop repeats, until it breaks out of the loop when next() returns None.

Unfortunately, Iterator::next() isn't a const function. Your actual implementation of next() for trivial data structures probably is constant, but it wants specifically Iterator::next() and that can't be labelled constant today because it's just an implementation of a trait and some other implementations are presumably not constant.

As a result, even though all Rust's loops are just "loop" and "loop" is allowed in a constant function, you can't use the for loop because Iterator::next() is never constant so now your function isn't either.

A proper fix for this would be pretty cool, but is not easy to do.

Yep, you're perfectly right of course. My "solution" was mainly to address the problem "how can I return a String from a const function"... the answer is that you don't have to :-)