Saturday, October 1, 2011

Rust basics: Numbers

Rust is a systems level language, it is my understanding that it provides the basic "types" of numbers that most system languages do.

There are two types of numbers that can be used by rust:
Integer literals, floating point literals.

Integer Literals

I figured that quoting from the authoritative spec means I can't be too wrong, it says:

An integer literal has one of three forms:
  1. A decimal literal starts with a decimal digit and continues with any mixture of decimal digits and underscores.
  2. A hex literal starts with the character sequence U+0030 U+0078 ("0x") and continues as any mixture hex digits and underscores.
  3. A binary literal starts with the character sequence U+0030 U+0062 ("0b") and continues as any mixture binary digits and underscores.
I don't understand the interest of allowing underscores,  I guess this means programmers can make values like 0xDEAD_BEEF_BABE or 0b_1111_0000 more readable.

Ah, but you can also specify the container (bitsize) size  of the object by  appending an integer suffix.  

  • The u suffix gives the literal type uint.
    The g suffix gives the literal type big.
  • "Signed" types i8, i16, i32 and i64
  • "unsigned" types u8, u16, u32, u64.

An example would be 0xDEAD_u32 or 18_i64 .  If you believe it the underscores can be placed anywhere that the programmer likes to make the values more readable.

Floating Point Literals

And again from the spec:

 A floating-point literal has one of two forms:
  1. Two decimal literals separated by a period character U+002E (’.’), with an optional
    exponent trailing after the second decimal literal.
  2. A single decimal literal followed by an exponent.

Nothing too exciting here, things like 0.01 and 99.999999 and 0.99.  You can also add two optional floating suffix values for your enjoyment.

  • The f32 suffix
  • The f64 suffix

These are  unsurprisingly specify the size of the container storing your value.

Example time !

So for a basic example:

use std;
import std::io;

fn main() {

    let stdout = io::stdout();

    let x = 127u;

    stdout.write_line(#fmt["X = %u", x]);

This as you'd expect outputs the number "127".  Nothing too complex.  We can see that the type of "x" is inferred as an unsigned integer.

So, lets do the usual thing and test assignment of a different type, does in infer correctly ?

$ rustc -o assignment 10:7 error: mismatched types: expected uint but found int (types differ)         x = 5;

Rust fails the compile, and graciously gives you the opportunity to fix this bug.  The quick and dirty solution is to change the assignment value to 5u, but if this value was created by another function you'd have to do what other languages consider casting, using the as keyword.

So you'd do it like this:

use std;
import std::io;

fn main() {

    let stdout = io::stdout();

    let     x = 127u8;
            x = (x + 128u8 ) as u8;

    stdout.write_line(#fmt["X = %u", x as uint]);

The reason why the equation 127 + 128 was chosen is because 255 is the largest value that can be stored in a 8 bit unsigned value. 

The first "as" in the above example is superfluous, but it remains as an example of how you can cast/convert a value that may be converted or passed by another function or module that you didn't write.

Note that in the above example no data was lost.  However if the final value of x is over 255, in the assignment line ( x = (x + 128u8 ) as u8 ) this is where the x value will be 'overflowed' not in the final stdout.write_line() function.

Much like C, Fortran, Algol or any other language when you use specific types you must be careful about overflowing the maximum.  I was reading the source code and came across the auto-expanding "big" type, but I'll explore that for another day.

I guess that is as good of a start as any.  I probably should put something about "using crates" but I don't know enough about them to be useful yet.

No comments: