Why your first FizzBuzz implementation may not work: an exploration into some initially surprising but great parts of Rust (though you still may not like them)

FizzBuzz is intended as a simple task, but in Rust there are a few pitfalls for the novice to be aware of. These aren’t problems with Rust but rather differences from what people are commonly familiar with, restrictions that may seem arduous at first but ultimately give you more power, at a small cost.

A simple functional implementation

OK, I said in the title that your first FizzBuzz implementation might not work—​then again, it might. You might write it like this which follows, for example, a way that works fine.

Figure 1: a simple FizzBuzz implementation with separate print statements: easy and functional.

(For brevity of the code examples, assume that the Rust code is always wrapped in fn main() { … }.)

Rust
for i in 1..101 {
    if i % 15 == 0 {
        println!("FizzBuzz");
    } else if i % 5 == 0 {
        println!("Buzz");
    } else if i % 3 == 0 {
        println!("Fizz");
    } else {
        println!("{}", i);
    }
}
Python
for i in range(1, 101):
    if i % 15 == 0:
        print('FizzBuzz')
    elif i % 5 == 0:
        print('Buzz')
    elif i % 3 == 0:
        print('Fizz')
    else:
        print(i)

These programs will both produce the desired output and they’re evidently very similar visually. The main thing to note about the Rust version is that println!() needs as its first argument a string literal, a formatting string; in Python it’d be equivalent to print('{}'.format(i)).

But how about if we decided that we wanted only one print call in the code base? Here’s how it might go:

Figure 2: storing the result in a variable, so as to use only one print statement.

if block as an expression; the whole result variable assignment is actually unnecessary, we could just put the if block straight where it’s subsequently used. Ruby programmers may recognise this as expression orientation. As a Python programmer when I came to Rust, I thought it just a gimmick to let you omit return; but I rapidly discovered that it’s a marvellous paradigm. Rust totally ruined Python for me.

Rust (won’t compile)
for i in 1..101 {
    let result = if i % 15 == 0 {
        "FizzBuzz"
    } else if i % 5 == 0 {
        "Buzz"
    } else if i % 3 == 0 {
        "Fizz"
    } else {
        i
    };
    println!("{}", result);
}
Python
for i in range(1, 101):
    if i % 15 == 0:
        result = 'FizzBuzz'
    elif i % 5 == 0:
        result = 'Buzz'
    elif i % 3 == 0:
        result = 'Fizz'
    else:
        result = i

    print(result)

This Rust code may seem all very well, but it’s actually not, owing to its strict typing rules. For what is the type of the variable result? The first three branches of the if‐expression produce strings and the fourth an integer:

Figure 2a: compiler output for Figure 2.

{integer} identifies a partially‐resolved type and is used for integer literals, when the compiler hasn’t yet determined what actual type it will be. An integer literal can be of type u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize or isize. If the compiler doesn’t get any hints as to what it should be, it’ll end up choosing i32.

error[E0308]: if and else have incompatible types
  --> <anon>:10:9
   |
7  |       } else if i % 3 == 0 {
   |  ____________-
8  | |         "Fizz"
   | |         ------ expected because of this
9  | |     } else {
10 | |         i
   | |         ^ expected `&str`, found integer
11 | |     };
   | |_____- `if` and `else` have incompatible types

error: aborting due to previous error

This clearly won’t do. How about we turn it into a string first?

Figure 3: adding .to_string() to the code from Figure 2.

for i in 1..101 {
    let result = if i % 15 == 0 {
        "FizzBuzz"
    } else if i % 5 == 0 {
        "Buzz"
    } else if i % 3 == 0 {
        "Fizz"
    } else {
        i.to_string()
    };
    println!("{}", result);
}

This is where we stray from the familiar parts of computer science that most people are familiar with to a part that far fewer people will relate to. Because this doesn’t work.

Figure 3a: compiler output for Figure 3.

Note that from Rust 1.58 this produces a spurious second error related to integer type inference; I’ve reported this, hopefully it’ll be resolved soon.

error[E0308]: if and else have incompatible types
  --> <anon>:10:9
   |
7  |       } else if i % 3 == 0 {
   |  ____________-
8  | |         "Fizz"
   | |         ------ expected because of this
9  | |     } else {
10 | |         i.to_string()
   | |         ^^^^^^^^^^^^^ expected `&str`, found struct `String`
11 | |     };
   | |_____- `if` and `else` have incompatible types

error: aborting due to previous error

“What?” I hear you saying; “aren’t they both strings? What’s the deal with this &str (how do I even pronounce it? [I can’t even decide whether to spell its indefinite article “a” or “an”. Sometimes I say “an ampersand str”, sometimes “a str”, sometimes “a str reference” or “a str slice” (inverted order). &'static str I call “a static str”. &'a str I mostly say something like “a str slice, lifetime a”.]) and String?” Now indeed I need to be more specific than I was in the previous analysis of the types of the branches: the first three branches didn’t just produce “strings”, they each produced a &str; the fourth branch produced a generic integer, which might be of a number of types (though in the absence of any further information it would become a i32, the default type of integer). Languages like Python, Ruby and JavaScript unify their integer types (actually, JavaScript unifies its number types altogether), while languages like C♯ and Java and Go have a variety of integral types of different sizes, so there being multiple integer types is familiar to many. But even C♯, Java and Go each have just one type of string.

Rust doesn’t. It has two.

Two types of strings? What is this?

We could look at a simple explanation and continue on our way, but we’ve come so far down this pathway that we might as well go the rest of the way—​understanding specifically what’s going on is definitely worthwhile. Why can C♯ and Java and Go get away with one String type and Rust can’t? To answer this, we have to step down to the level of memory management.

C♯, Java and Go are all managed languages (also known as garbage collected languages). That is, they have a runtime which takes care of the allocation and freeing of memory at the appropriate times; when nothing is using the string any more, it can be freed. They can thus return references to a string and not need to worry about liveness issues: parts of a string that are in use will not be freed.

There is a trade‐off here for them; as a general rule, such languages have immutable strings—​if you concatenate two strings, it involves a completely new allocation. (This means that a solution that modifies a string by adding “Fizz” if it’s divisible by three and then “Buzz” if it’s divisible by five and then the number if the string is still empty may well involve two allocations for multiples of fifteen, though something called string interning which is commonly employed may reduce or fix that, depending on how it’s done and how clever any optimiser is.) This is, I presume, because the alternatives are worse: copying for any slicing will lead to a very significant increase in memory usage for most programs; and allowing mutation of one string to potentially affect others derived from it is terrible for correctness, and it could lead to nasty data race conditions in what is for practical purposes a primitive type, and it could also, for anything using UTF‐16 or UTF‐8, lead to illegal code points in a subslice (substring); sure, most of these problems arise in other places, but having it in their string types would be much worse. (I said that they only have one string type, but that also is typically not quite true—​there are enough cases that need more efficient mutable strings that they tend to have such a type. For example, Java and .NET have things called StringBuilder.)

Rust’s model is somewhat different, not being a garbage collected language, being centred around its language-wide model of ownership, where each object is owned in one place at a time, though other places may safely borrow references to it.

String is an owned type. That is, it has exclusive ownership of the contents of the string; and when it passes out of scope, the memory for the contents of the string will be freed immediately. For this reason, any substring can’t be of the type String, for there will be no connection between the two, and so when one passed out of scope, the other would become invalid, leading to a loss of memory safety. And so instead it is that slices (substrings) use a type which is a reference to the contents that something else owns—​&str. Rust, through its concept of lifetimes, is able to guarantee that no slice outlives the actual String, and so memory safety is ensured.

Lifetimes have syntax, 'like_this, with one special lifetime 'static that indicates the life of the program. But when there’s only one possible value or an obvious way things should be hooked up, you can normally skip writing the lifetime; this is called lifetime elision. We’ll encounter lifetimes a bit later in this article, but so far they’ve all been elided. Just to prepare you: a string slice of lifetime a is spelled &'a str, and string literals (being stored in the binary) are &'static str.

If you’re interested in learning more about lifetimes at this point, the Rust Book has a chapter on them.

Back to the fizzing and buzzing

So then, the problem is that one of them is an owned string while the other three are string slices (references to statically defined strings). How shall we resolve it? How about we try making them all string slices (that is, of type &str):

for i in 1..101 {
    let result = if i % 15 == 0 {
        "FizzBuzz"
    } else if i % 5 == 0 {
        "Buzz"
    } else if i % 3 == 0 {
        "Fizz"
    } else {
        &*i.to_string()
    };
    println!("{}", result);
}

Figure 4: adding &* to the code from Figure 3 to convert the String to &str.

String dereferences to str—​viz., it implements Deref<Target = str>—​so &*string produces a string slice, type &str, pointing to the contents of the String.

That seems like a good idea, right? Sorry, that won’t do:

Figure 4a: compiler output for Figure 4.

error[E0716]: temporary value dropped while borrowed
  --> <anon>:10:11
   |
7  |       } else if i % 3 == 0 {
   |  ____________-
8  | |         "Fizz"
9  | |     } else {
10 | |         &*i.to_string()
   | |           ^^^^^^^^^^^^^ creates a temporary which is freed while still in use
11 | |     };
   | |     -
   | |     |
   | |_____temporary value is freed at the end of this statement
   |       borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value

error: aborting due to previous error

Here we’re running into a lifetime issue; the String created by i.to_string() is not being stored anywhere, and so it is freed at the end of the else block. Thus the reference to it can’t escape that block, like we were trying to make happen. This is a memory safety bug that the Rust compiler caught; in some languages it would wind up as a dangling pointer, which is a Very Bad Thing.

Now in this case, the compiler gave us a suggestion: “consider using a `let` binding to create a longer lived value”. Not all of the compiler’s suggestions will be workable, but I think this one always is if it makes it. We can indeed lift the String variable to outside the else block; the compiler was only caring that the object being referenced needed to be valid for the body of the for loop. Here’s how that looks:

for i in 1..101 {
    let x;
    let result = if i % 15 == 0 {
        "FizzBuzz"
    } else if i % 5 == 0 {
        "Buzz"
    } else if i % 3 == 0 {
        "Fizz"
    } else {
        x = i.to_string();
        &*x
    };
    println!("{}", result);
}

Figure 5: taking a reference as in Figure 4, but storing the owned string in the outer scope: this works.

This way, the String lives until the end of the for‐loop, which is longer than result needs to be valid for, and so result is allowed to refer to it.

How about we make it a String?

Another direction that we could go is to make all of the branches return owned strings:

for i in 1..101 {
    let result = if i % 15 == 0 {
        "FizzBuzz".to_string()
    } else if i % 5 == 0 {
        "Buzz".to_string()
    } else if i % 3 == 0 {
        "Fizz".to_string()
    } else {
        i.to_string()
    };
    println!("{}", result);
}

Figure 6: String all the way: this works, at a runtime cost.

This will work fine, but it has meant that a new chunk of memory is allocated for all values of i, rather than just for the ones which aren’t factors of three or five.

Making it a function

We’ve gone as far as we can in this direction without things becoming absurd; how about if we alter the parameters of the problem so that we’re not printing the result, but rather returning it from a function?

Here’s what we might start with:

Figure 7: the fizz_buzz function, with Strings.

Rust
fn fizz_buzz(i: i32) -> String {
    if i % 15 == 0 {
        "FizzBuzz".to_string()
    } else if i % 5 == 0 {
        "Buzz".to_string()
    } else if i % 3 == 0 {
        "Fizz".to_string()
    } else {
        i.to_string()
    }
}

for i in 1..101 {
    println!("{}", fizz_buzz(i));
}
Python
def fizz_buzz(i):
    if i % 15 == 0:
        return 'FizzBuzz'
    elif i % 5 == 0:
        return 'Buzz'
    elif i % 3 == 0:
        return 'Fizz'
    else:
        return i

​

for i in range(1, 101):
    print(fizz_buzz(i))

We now have an extra layer of encapsulation around it which demonstrates clearly that the solution when we were producing a &str of hoisting the String variable declaration x to the loop won’t work, because that variable would be going out of scope also, being contained inside the function. (Try it if you’re not sure; the return type is unrepresentable in Rust’s type system, as there is no suitable lifetime—​x won’t live for 'static (the duration of the process) and there’s nothing else to tie it to.)

And so, simply because we’ve put it in a function, we have a little inefficiency—​we’re allocating new strings for multiples of three or five when it shouldn’t be necessary.

Enter Cow<str>

Fortunately, Rust has algebraic data types (known as enums in the language), and there’s a convenient type defined in the standard library as something that is either a string slice or an owned string.

Here are the relevant definitions (without any of the methods that make it more useful):

pub enum Cow<'a, B: ?Sized + 'a> where B: ToOwned {
    Borrowed(&'a B),
    Owned(<B as ToOwned>::Owned)
}

impl ToOwned for str {
    type Owned = String;

    fn to_owned(&self) -> String {
        …
    }
}

Figure 8: the std::borrow::Cow definition and how Cow<'a, str> comes from that.

The ToOwned implementation is defined in library/alloc/src/str.rs in the Rust repository, if you’re interested; but it doesn’t really matter.

And lo! Lifetime syntax makes an appearance.

I’ll admit that to be somewhat opaque to the inexperienced, so let’s fill in the generics to get an idea of approximately what it ends up for real life:

pub enum Cow<'a, str> {
    Borrowed(&'a str),
    Owned(String),
}

Figure 9: an approximation of Cow<str>.

(This is for demonstration purposes only; the grammar is not correct.)

Now the particular type that we wish to pay attention to is Cow<'static, str>, which is either a &'static str or a String, and which dereferences to str [Cow<B> implements Deref<Target = B>; put another way, given a Cow<str> x, &*x produces a &str and you can mostly just treat a Cow<'a, str> as a &'a str.]). Why is this type so interesting for us? Because it is completely owned data, containing no non‐'static lifetimes. In other words, if we create one of these inside a function, it is entirely self‐contained, not having any references to things inside the function and we can return it without any trouble. Expressed even more sloppily: this is a type that can contain a string literal, or an owned string that you have constructed.

For assistance in creating these objects, there are implementations of From for Cow<str>; thanks to these, if you have a &str or a String, you can write Cow::from(x) or, where it can be inferred that the result must be Cow<str>, x.into(), instead of having to write the specific enum variant, Cow::Borrowed(x) or Cow::Owned(x).

As mentioned, Cow<str> dereferences to str, allowing us to very often treat it as though it were a &str. A prime example of this is the manner in which we can use it directly in string formatting, using that type’s std::fmt::Display implementation with the {} format string (fmt::Display is the direct parallel of __str__() in Python’s string formatting, or to_s(), toString(), &c. in various other languages, though it works directly with a writer allowing you to skip the writing to an intermediate string if you want; the to_string() method on any type implementing fmt::Display uses this mechanism to produce a String).

Here’s what it looks like:

use std::borrow::Cow;

fn fizz_buzz(i: i32) -> Cow<'static, str> {
    if i % 15 == 0 {
        "FizzBuzz".into()
    } else if i % 5 == 0 {
        "Buzz".into()
    } else if i % 3 == 0 {
        "Fizz".into()
    } else {
        i.to_string().into()
    }
}

for i in 1..101 {
    println!("{}", fizz_buzz(i));
}

Figure 10: a FizzBuzz function returning Cow<'static, str>: this works.

Great! We’ve reduced the work the computer needs to do and made our admittedly trivial case faster.

But can we go further?

Writing our own enum and implementing std::fmt::Display efficiently

Of course, what we’re representing for each term in the sequence isn’t actually “a static string slice or an owned string”, it’s “‘Fizz’, ‘Buzz’, ‘FizzBuzz’ or a number”. We just converted all the choices to strings eagerly; we could just as easily make it lazy, avoiding the extra allocation where possible (and in fact they are all avoidable).

Let’s make an enum of our own, which we’ll call Term.

While we’re at it, we’ll implement std::fmt::Display, which means that we can print to stdout without even needing to create the intermediate String. We’ll also implement std::fmt::Debug, which answers to the format string {:?} and is for giving a developer-oriented representation of the object, akin to Python’s __repr__() and Ruby’s inspect; it can be just the same thing in this case.

Figure 11: using a dedicated data type to represent the real possibilities efficiently.

The suggested Python code is thoroughly contrived, as is going to be the case in all languages without algebraic data types; I provide it for the sake of users not familiar with algebraic data types to at least give an impression of how it works.

Rust
use std::fmt;

enum Term {
    Fizz,
    Buzz,
    FizzBuzz,
    Number(i32),
}

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

impl fmt::Debug for Term {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Display::fmt(self, f)
    }
}

fn fizz_buzz(i: i32) -> Term {
    if i % 15 == 0 {
        Term::FizzBuzz
    } else if i % 5 == 0 {
        Term::Buzz
    } else if i % 3 == 0 {
        Term::Fizz
    } else {
        Term::Number(i)
    }
}

for i in 1..101 {
    println!("{}", fizz_buzz(i));
}
Python analogue (thoroughly contrived)
class Term:

    def __init__(self, value):
        self._value = value

    def __str__(self):
        if self is Term.Fizz:
            return "Fizz"
        elif self is Term.Buzz:
            return "Buzz"
        elif self is Term.FizzBuzz:
            return "FizzBuzz"
        else:
            return str(self._value)

    def __repr__(self):
        return str(self)

    @staticmethod
    def Number(number):
        return Term(number)

# Pretend that these are opaque
Term.Fizz = Term(object())
Term.Buzz = Term(object())
Term.FizzBuzz = Term(object())

def fizz_buzz(i):
    if i % 15 == 0:
        return Term.FizzBuzz
    elif i % 5 == 0:
        return Term.Buzz
    elif i % 3 == 0:
        return Term.Fizz
    else:
        return Term.Number(i)

for i in range(1, 101):
    print(fizz_buzz(i))

Observe also that this is doing a really good job of actually representing the data contained, though for this trivial case it wouldn’t matter much if it didn’t. We could just as easily have replaced the first three variants of that enum with a single Word(&'static str) variant, using Word("FizzBuzz") et al. for the values. (In fact, that was actually the first version I wrote of this step of the process—​see, even I was fixed on using strings where it’s simply unnecessary!)

We could go further, showing how to write a dedicated iterator, but with how Rust’s iterator chaining works, this isn’t really necessary—​you can just write (1..101).map(fizz_buzz). That gives a lot more flexibility; as soon as you have something implementing Iterator<Item = i32>, you can just tack .map(fizz_buzz) onto the end of it and you have a type implementing Iterator<Item = Term>.

That loop could be rewritten in this style, incidentally:

Figure 12: mapping an integer iterator to the fizz_buzz function.

Rust
for f in (1..101).map(fizz_buzz) {
    println!("{}", f);
}
Python
for f in map(fizz_buzz, range(1, 101)):
    print(f)

Whichever of these sorts of ways we choose to do it, we end up with the good ol’ FizzBuzz lines:

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
Fizz
22
23
Fizz
Buzz
26
Fizz
28
29
FizzBuzz
31
32
Fizz
34
Buzz
Fizz
37
38
Fizz
Buzz
41
Fizz
43
44
FizzBuzz
46
47
Fizz
49
Buzz
Fizz
52
53
Fizz
Buzz
56
Fizz
58
59
FizzBuzz
61
62
Fizz
64
Buzz
Fizz
67
68
Fizz
Buzz
71
Fizz
73
74
FizzBuzz
76
77
Fizz
79
Buzz
Fizz
82
83
Fizz
Buzz
86
Fizz
88
89
FizzBuzz
91
92
Fizz
94
Buzz
Fizz
97
98
Fizz
Buzz

Conclusion

So now you have it: why your first FizzBuzz implementation in Rust might not work. Some of the difficult parts of it are typical of statically typed languages, and some more of them are more specific to Rust. (As a matter of fact, the situation would be similar in C++, but C++ lets you do all sorts of silly things and doesn’t give much in the way of memory safety. Don’t bother arguing with me on that point though, I’m just going off the word of others—​I don’t know C++ especially well.)

We’ve covered how Rust’s ownership model can prevent you from writing things in certain ways that you might be used to, and why (though not the concrete benefits of doing this); also how Rust is able to express more efficient concepts; we’ve seen how enums (algebraic data types) can be used to model data more precisely and efficiently.

Hopefully you’ve seen the power of this and it interests you.

Is this an increased conceptual burden? Yes.

Is it a nuisance? Occasionally. (My experience is that it saves trouble at least as often as it causes it.)

Does it allow you to improve your program’s efficiency? Certainly, and with complete confidence. While in the past these things have required a loss of certainty about safety and correctness properties, in Rust you don’t need to make it a tradeoff.

Does it make code easier to reason about? In simple cases like this you won’t see that, but these general attributes of the ownership model and algebraic data types really do help in more complex cases. (I really miss these things when working in Python.)

These matters end up both a good thing and a bad thing about Rust; sometimes you will love them, and occasionally you will hate them. But I at least don’t hate them often at all.

Should you use Rust? Well, I would suggest that you at least try it if you haven’t. You may well find it not ready or fit for your purposes. Because of its focus on systems development, it is more cumbersome for many higher‐level tasks, though even on those it can be surprisingly effective.

Finally, if you’re not familiar with Rust or got lost at any point, I suggest you go through the official documentation; the Rust book has sections dealing with lifetimes and ownership, enums and a whole lot more. I also wrote Rust ownership the hard way, an article that you may find helpful in understanding this crucial defining feature of Rust. If you still have questions, places like #rust-beginners on irc.mozilla.org are great. (Pretty much everyone agrees that the Rust community is superb. You will get good answers there.)

Appendix: other FizzBuzz techniques

This section is by no means comprehensive, but includes a few extras that have been pointed out over time.

1. match instead of if

People often suggest replacing the chained conditionals with a match block, which incidentally lets you skip the “superfluous” i % 15:

for i in 1..101 {
    match (i % 3, i % 5) {
        (0, 0) => println!("FizzBuzz"),
        (_, 0) => println!("Buzz"),
        (0, _) => println!("Fizz"),
        (_, _) => println!("{}", i),
    }
}

Figure 13: Figure 1, but with match instead of chained if.

Application of this technique to other examples is left as an exercise for the reader.

Opinions differ on which is nicer. In this particular case I personally prefer the if version, but if you introduced a third word I’d switch to the match version.

2. std::fmt::Display trait objects

This possibility was brought to my attention in September 2019, sitting somewhere between the code in Figure 2 and the code in Figure 11, by using a dynamic trait object:

for i in 1..101 {
    let result: &dyn std::fmt::Display = if i % 15 == 0 {
        &"FizzBuzz"
    } else if i % 5 == 0 {
        &"Buzz"
    } else if i % 3 == 0 {
        &"Fizz"
    } else {
        &i
    };
    println!("{}", result);
}

Figure 14: making Figure 2 work by the use of std::fmt::Display trait objects.

Application of this technique to other examples is again left as an exercise for the reader.

This is a worthwhile technique to be aware of: if there exists a trait that covers the required functionality shared between the types you wish to store in the one variable, a dynamic trait object can be a viable option instead of making your own enum. If you wanted to be able to inspect the values to determine what they semantically were, this would be insufficient, and something like the Term enum would be called for, but so long as all you want to do is format them for display, a std::fmt::Display trait object is fine.

3. And if you really like fiddling with FizzBuzz optimisation…

Feel free. Here’s the final conclusion of that lot, updated to compile now, plus making OUT a byte string for improved legibility (!?):

#![no_std]
#![feature(lang_items, start, rustc_private)]

extern crate libc;

static OUT: [u8; 413] = *b"\
    1\n2\nFizz\n4\nBuzz\nFizz\n7\n8\nFizz\nBuzz\n11\nFizz\n13\n14\nFizzBuzz\n\
    16\n17\nFizz\n19\nBuzz\nFizz\n22\n23\nFizz\nBuzz\n26\nFizz\n28\n29\nFizzBuzz\n\
    31\n32\nFizz\n34\nBuzz\nFizz\n37\n38\nFizz\nBuzz\n41\nFizz\n43\n44\nFizzBuzz\n\
    46\n47\nFizz\n49\nBuzz\nFizz\n52\n53\nFizz\nBuzz\n56\nFizz\n58\n59\nFizzBuzz\n\
    61\n62\nFizz\n64\nBuzz\nFizz\n67\n68\nFizz\nBuzz\n71\nFizz\n73\n74\nFizzBuzz\n\
    76\n77\nFizz\n79\nBuzz\nFizz\n82\n83\nFizz\nBuzz\n86\nFizz\n88\n89\nFizzBuzz\n\
    91\n92\nFizz\n94\nBuzz\nFizz\n97\n98\nFizz\nBuzz\n";

#[start]
fn start(_argc: isize, _argv: *const *const u8) -> isize {
    unsafe {
        core::arch::asm!(
            "syscall",
            in("rax") 1, // syscall number = write
            in("rdi") 1, // fd = stdout
            in("rsi") OUT.as_ptr(),
            in("rdx") OUT.len(),
            out("rcx") _,
            out("r11") _,
            lateout("rax") _,
        );
    }
    0
}

#[lang = "eh_personality"] extern fn eh_personality() {}
#[panic_handler] extern fn panic_handler(_: &core::panic::PanicInfo) -> ! { loop {} }

Figure 15: optimising the FizzBuzz problem roughly as far as humanly possible.

Applying this technique should be left unexercised by the reader: it’s provided for demonstration purposes only, and should not be attempted at home or at work.

(This code depends on Linux syscall conventions, so it won’t work on other platforms.)