Chris Morgan

I’m a software developer, dealing mostly in web things.

I’m also (more importantly) a committed Christian—please talk to me at any time about that.

Elsewhere

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

Person standing at a bookshelf of books marked 1, 2, Fizz, 4, Buzz, et cetera up to 16, wondering which one to borrow

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.

This article has been kept up to date and currently requires Rust 1.0; Python code will run in both Python 2 and Python 3.

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.

For brevity of the code examples, assume that the Rust code is wrapped in fn main() { }. If you’re worried about the conciseness of the Python code relative to the Rust code,

A simple FizzBuzz implementation with separate print statements: easy and functional.

from __future__ import braces
 
def main() {
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);
}
}
}
 
if __name__ == '__main__' {
main()
}

Alternative 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)

Python

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);
}
}

Rust

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:

FizzBuzz with one print statement only.

With suitable consideration,
And also a little gyration:
A bad idea this,
I’ll give it a miss;
Use Python of normal formation.

Alternative Python: conclusions.

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)

Python

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);
}

Rust (won’t compile)

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:

f.rs:7:12: 11:6 error: if and else have incompatible types:
expected `&'static str`,
found `_`
(expected &-ptr,
found integral variable) [E0308]
f.rs:7 } else if i % 3 == 0 {
f.rs:8 "Fizz"
f.rs:9 } else {
f.rs:10 i
f.rs:11 };
f.rs:7:12: 11:6 help: pass `--explain E0308` to see a detailed explanation
error: aborting due to previous error

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

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.

f.rs:7:12: 11:6 error: if and else have incompatible types:
expected `&'static str`,
found `collections::string::String`
(expected &-ptr,
found struct `collections::string::String`) [E0308]
f.rs:7 } else if i % 3 == 0 {
f.rs:8 "Fizz"
f.rs:9 } else {
f.rs:10 i.to_string()
f.rs:11 };
f.rs:7:12: 11:6 help: pass `--explain E0308` to see a detailed explanation
error: aborting due to previous error

“What?” I hear you saying; “aren’t they both strings? What’s the deal with this &'static str (how do I even pronounce it?) and collections::string::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 &'static 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 alternative of copying for any slicing is a greater evil still, 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; also it could, for anything using UTF‐16 or UTF‐8, lead to illegal codepoints in a subslice; sure, most of these problems arise in other places, but having it in their string types would be much, 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.

collections::string::String is an owned type. That is, it has exclusive ownership of the contents of the string; when it passes out of scope, the contents of the string will be freed. 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 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.

There’s a more detailed explanation of lifetimes in The Rust References and Lifetimes Guide; here, just know that if there’s a 'thing_like_this after the & of a reference type, it’s the lifetime of the reference. 'static is a special lifetime meaning “the duration of the program”, meaning that it must be hard‐coded into the binary, such as with a string literal—hence the type of a string literal, &'static str.

Back to the fizzing and buzzing

So then, the problem is that one of them is an owned string while the other three are static 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—any &'a T can coerce implicitly to &'b T if 'a is longer than 'b, and 'static is longer than any other lifetime, and if we omit the lifetime in a type the compiler will infer the appropriate lifetime):

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

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);
}

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

f.rs:10:11: 10:24 error: borrowed value does not live long enough
f.rs:10 &*i.to_string()
^~~~~~~~~~~~~
f.rs:11:7: 13:2 note: reference must be valid for the block suffix following statement 0 at 11:6...
f.rs:11 };
f.rs:12 println!("{}", result);
f.rs:13 }
f.rs:9:12: 11:6 note: ...but borrowed value is only valid for the expression at 9:11
f.rs:9 } else {
f.rs:10 &*i.to_string()
f.rs:11 };
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, we can just 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. Sometimes you’ll encounter situations where this is an acceptable solution, but very often it won’t be.

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);
}

Taking a reference while storing the owned string in the outer scope: this works.

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);
}

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:

The fizz_buzz function, with Strings.

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))

Python

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));
}

Rust

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 'static 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):

// In std::borrow:
pub enum Cow<'a, B: ?Sized + 'a> where B: ToOwned {
Borrowed(&'a B),
Owned(<B as ToOwned>::Owned)
}
 
// Somewhere that doesn’t matter (src/libcollections/str.rs, if you care):
impl ToOwned for str {
type Owned = String;
 
fn to_owned(&self) -> String {

}
}

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

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 (this is to convey the meaning; the grammar is not correct):

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

(Remember, this is for demonstration purposes only.)

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 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 &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.

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));
}

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 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.

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.

Using a dedicated data type to represent the real possibilities efficiently.

class FizzBuzzItem:
 
def __init__(self, value):
self._value = value
 
def __str__(self):
if self is FizzBuzzItem.Fizz:
return "Fizz"
elif self is FizzBuzzItem.Buzz:
return "Buzz"
elif self is FizzBuzzItem.FizzBuzz:
return "FizzBuzz"
else:
return str(self._value)
 
def __repr__(self):
return str(self)
 
@staticmethod
def Number(number):
return FizzBuzzItem(number)
 
# Pretend that these are opaque
FizzBuzzItem.Fizz = FizzBuzzItem(object())
FizzBuzzItem.Buzz = FizzBuzzItem(object())
FizzBuzzItem.FizzBuzz = FizzBuzzItem(object())
 
def fizz_buzz(i):
if i % 15 == 0:
return FizzBuzzItem.FizzBuzz
elif i % 5 == 0:
return FizzBuzzItem.Buzz
elif i % 3 == 0:
return FizzBuzzItem.Fizz
else:
return FizzBuzzItem.Number(i)
 
for i in range(1, 101):
print(fizz_buzz(i))

Python analogue (thoroughly contrived)

use std::fmt;
 
enum FizzBuzzItem {
Fizz,
Buzz,
FizzBuzz,
Number(i32),
}
 
impl fmt::Display for FizzBuzzItem {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
FizzBuzzItem::Fizz => f.write_str("Fizz"),
FizzBuzzItem::Buzz => f.write_str("Buzz"),
FizzBuzzItem::FizzBuzz => f.write_str("FizzBuzz"),
FizzBuzzItem::Number(num) => write!(f, "{}", num),
}
}
}
 
impl fmt::Debug for FizzBuzzItem {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
 
fn fizz_buzz(i: i32) -> FizzBuzzItem {
if i % 15 == 0 {
FizzBuzzItem::FizzBuzz
} else if i % 5 == 0 {
FizzBuzzItem::Buzz
} else if i % 3 == 0 {
FizzBuzzItem::Fizz
} else {
FizzBuzzItem::Number(i)
}
}
 
for i in 1..101 {
println!("{}", fizz_buzz(i));
}

Rust

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 = FizzBuzzItem>.

That loop could be rewritten in this style, incidentally:

Mapping an integer iterator to the fizz_buzz function.

for f in map(fizz_buzz, range(1, 101)):
print(f)

Python

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

Rust

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++ well at all.)

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’ve really been missing these things in Python.)

These matters end up both a good thing and a bad thing about Rust; sometimes you will love them, sometimes 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 I believe that when ready it will be a great language for things like web development, as I spoke about in my talk at Strange Loop last month (you can also look directly at the slides as 2MB of SVG).

Finally, if you’re not familiar with Rust or got lost at any point, I suggest you go through the official documentation; the 30‐minute introduction to Rust treats the concept of ownership quite well, and the book deals with enums and a whole lot more. If you still have questions, places like #rust on irc.mozilla.org are great—I’m on quite a lot of the time, as ChrisMorgan.

And if you really like fiddling with FizzBuzz optimisation…

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

#![no_std]
#![feature(asm, lang_items, libc, no_std, start)]
 
extern crate libc;
 
const LEN: usize = 413;
static OUT: [u8; LEN] = *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 {
asm!(
"
mov $$1, %rax
mov $$1, %rdi
mov $0, %rsi
mov $1, %rdx
syscall
"
:
: "r" (&OUT[0]) "r" (LEN)
: "rax", "rdi", "rsi", "rdx"
:
);
}
0
}
 
#[lang = "stack_exhausted"] extern fn stack_exhausted() {}
#[lang = "eh_personality"] extern fn eh_personality() {}
#[lang = "panic_fmt"] extern fn panic_fmt() {}

Comments? Questions? Corrections? If you want to contact me about anything in this post, write to me at @__chrismorgan or email me.