Chris Morgan › Blog 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. Tagged: Rust

Published: October 3, 2014

Last updated: May 22, 2020 This article is intended to teach beginners about the Rust programming language. Displayed compiler output is from rustc 1.38.0-nightly.

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 | = note: expected type `&str` found type `{integer}` 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. 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 `std::string::String` 11 | | }; | |_____- if and else have incompatible types | = note: expected type `&str` found type `{std::string::String}` 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 std::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.

std::string::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 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 Book; 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 . If you know a little more about Rust types, you may wonder why String is used instead of Box<str> . The answer lies in the difference between the two types: Box<str> is a fixed‐size allocation, so you’d need to reallocate every time you push to the string, but String overallocates for efficiency in the usual case, storing a capacity member also to track that. So String is normally the more suitable of the two. Things really aren’t so very different from how they are in most other languages; it really is just that the lack of mandatory garbage collection stops some uses of &str , making it so that you have to reach for the growable string buffer more often than you would have to in other languages, because they allow you to be sloppy with their main string type.

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): 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, 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); } Figure 5: taking a reference as in Figure 4, but 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); } 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 String s. 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 '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): 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 src/liballoc/str.rs in the Rust repository, if you’re interested; but it doesn’t really matter.

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.

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…