29 Jan 24
Rust Numbers
In the last post, we discussed Strings. In this post we are going to look at the differences between Ruby numbers and Rust’s various number types.
Numbers, either integers or floats, are pretty straight forward in Ruby. You simply declare the value and the interpreter will decide if it is an Integer or a Float. Example Ruby code below:
If you are familiar with Ruby, you may take for granted how easy it is to declare a numeric data type and not really think about the differences between a whole integer and a float with a decimal. Not until you have to explicitly control precision maybe. Now let’s look at the Rust equivalent:
Woah! You may be asking what an i32 and f32 are. Rust is more explicit about numeric data declaration and it is wonderful. If you have written any heavy math in Ruby, you may have spent time figuring out where nulls were coming from, or why you were receiving Integers when you anticipated a Float. The Rust compiler will catch those differences and give clear and meaningful feedback. Let’s show an example:
If you try to run the above code with cargo run
, you’ll receive the following feedback from the compiler:
This is not unique to Rust, many typed languages can provide similar feedback, but in Ruby this would not be caught. Let’s embrace this further and dig into these types more.
Let’s discuss what an i32 is in more detail. It means a 32 bit integer. 32 bits means a range of numbers from -2,147,483,648 to 2,147,483,647. That’s a pretty big range.
There are related ranges. For example, an i8 is an 8 bit integer. It has a range of -128 to 127. Notice those ranges contain negative and positive numbers. If you perhaps know that you do not want to permit negative numbers, there is a u8 primitive type as well. It has a range of 0 to 255. Let’s see this action:
Easy enough, the output is 255
. What happens if we try assigning a negative value:
The compiler provides such great feedback. It immediately tells us that we have a variable assigned a u8 data type and we’ve tried assigning an invalid value. The compiler can really be useful. Let’s see what it says when we try to mutate our previous variable that is right at the maximum value of the u8 range.
This results in the process panicking, with the following error:
Notice that is not a compilation error. That is a runtime error. When we try to overflow the number to a value outside of the data types range, the process panics. We still have to be careful not to use invalid values at runtime.
So let’s get into some operators and see how basic arithmetic compares between Ruby and Rust.
Pay close attention to line 18 and 21. We’ll pull them out for better inspection:
let float_addition: f32 = (left as f32 + 0.23 as f32) as f32;
Is all of that really needed just to add an integer to a float? Well, yes, kinda. At least an understanding of what is required. Notice in the assignment (the left side) we declare the value as a type f32. So our intention is to have a 32-bit float value. On the right side, we have to cast the left
value as a f32 because its original value of u8 doesn’t have an add method that supports a float. So left as f32
casts the value to a float and then we add 0.23.
That’s quite a bit different from Ruby but is a good illustration of the more explicitness we’ll find around numbers in Rust. We’ll stop there for now and go into Number data types and their operations in more detail in future posts where we address common use cases.
If you know of someone using Ruby that wants to learn about Rust, please share this newsletter with them. If a community starts to form, we can spin up a Slack instance to help each other work through examples and answer questions.
Music companion of this post: Underviewer - Wonders and Monsters
What I am working on currently: I am multitasking at the moment: expanding the API for my startup–Schemabook, and implementing features in a Rust based system that uses WASM.