Rust: Basic Language Use

Khalid Hourani
7 min readFeb 25, 2019

Control Abstraction

The flow of a Rust program can be controlled by if expressions, loops, for loops, and while loops.

If expressions function by evaluating the truth-value of a statement and executing the code if the value is true. For example,

yields the following output

If statements can be paired with else and else if statements:

A loop executes until it reaches a break statement. In the following example, the loop repeats indefinitely, printing the statement on line 3 until issued a manual stop (usually Ctrl+C) directly.

On the other hand, the following code will execute until the break statement:

The above code is common enough that a while loop serves its purpose more generally. Like an if statement which checks the truth value of a statement, a while loop executes the code within its block until the truth value of the statement is false. For example:

Finally, a for loop iterates through the contents of an iterable object, such as an array. The following code demonstrates this by iterating through an array directly:

You can similarly iterate through a range by using the Range type, provided by the standard library. To iterate from i to j - 1, simply write

for number in i..j {
//code
}

as in the following example:

Data types

Rust is a statically typed language, which means that every variable’s type must be declared. This allows for type checking at compile time. For example, if we try to combine an integer and a character, something that is not allowed in Rust, the compiler will output an error. For example, the following code

outputs an error

Rust distinguishes between Scalar and Compound types. Scalar types are types that represent a single value. The scalar types in Rust are integers, floating-point numbers, Booleans, and characters.

The data range of an n-bit signed integer is -2^{n-1} to 2^{n-1}-1. For an unsigned integer, it is 0 to 2^{n-1}. Rust allows for 8, 16, 32, 64, and 128 bit integers, though it defaults to 32 bits if the integer type is unspecified. The type can be specified as in the following image:

It should be noted that Rust will perform Two’s Complement Wrapping when integers overflow; if an 8-bit signed integer with value 127 is incremented, its value will change to -128.

Floating-Point numbers are either 32 bits or 64 bits. By default, Rust uses a 64 bit floating point number.

Booleans contain values True and False.

A character in Rust corresponds to the Unicode Scalar Value of that character. For example,

let z = 'ℤ';

will assign “U+2124,” the corresponding Unicode Scalar Value for “ℤ” to z.

Compound types, on the other hand, represent multiple values. These consist of tuples and arrays. A tuple is an immutable collection of a finite number of possibly different data types into a single object. For example, a tuple can contain both an integer and a float, as in:

let x = (1, 1.0);

Or, equivalently:

let x : (i32, f64) = (1, 1.0);

which will assign the tuple (1, 1.0) to variable x. An array, however, is a collection of a specified number of objects of the same type. For example, we can declare an integer array with:

let integer_array = [1, 2, 3, 4];

Equivalently, we can write:

let integer_array : [i32; 4] = [1, 2, 3, 4];

Data Abstraction

Data abstraction is a way of masking the details of how data is represented in code. With the separation of interface and implementation, information can be presented in a format that is simple for the user.

Data abstraction eases the complexity in the user code. If an error arises or bugs are reported, the source of the error will lie within the implementation code. Because of the separation between interface and implementation, changes to the implementation code are possible without having to make changes to the user code.

Data Abstraction in Rust comes in a couple forms:

Structs in Rust are used whenever there’s a need for an object with multiple variables. They are similar to tuples, but allow the user more flexibility by letting them name the variables and choose their data types. A user can then call an instance of the struct to create an object with the said variables already declared. This prevents the programmer from needing to recreate variables for a commonly used object, and also keeps them from dealing with potentially unnecessary information within their code.

A struct is defined — predictably — using the keyword struct, and then listing the variable names followed by their data types, separated by a colon. The variable list is surrounded by curly braces.

Example of a “User” Struct

Now you can create an instance of a struct in the same way you declare a regular variable, only this time you need to specify the values for all of the variables, like so:

Creating an Instance of the User Struct

Notice how the values for the variables are filled. Since their data types are already determined, only the value needs to be created, hence the use of a colon in the struct creation. Another thing to note is that the order in which the variables are named does not matter.

Now the struct can be treated as its own object. To pull out specific variables from the it, dot notation is used:

You can also create a function that returns a struct object with the filled variables. It is done in a similar fashion to creating a function that returns a regular variable, only the values in the struct need to be filled in a “key: value” manner.

build_user Function to Create User

Another neat feature Rust provides is a form of defining shared behavior called traits. A trait communicates with the Rust compiler so that different types can share some functionality. In the case of structs, two different types can share a common function that processes data from each type and produces a similar result.

For example, we have a Student struct that holds defining attributes, such as name and student ID, and a Professor struct that holds similar information. Let’s say we want to have a form to request a summary of these basic details from these two different types.

Here we use the trait keyword and the trait’s name.

Implementing the trait is now possible. The difference in the implementation for Student’s summarize trait and Professor’s can be seen in the image above.

Now, we can call the methods on instances of Student or Professor. With traits, we can hide the details of shared behavior.

Expressions

In general, an expression is a combination of variables, constants, functions, and operators that computes to a value. In Rust, expressions always evaluate to a value and sometimes have side effects, a situation in which the state outside of a local environment is modified.

We can nest expressions within expressions to create subexpressions. Thus, the structure of expressions dictates the structure of execution. Rust includes many types of expressions, including block expressions, statements, literal expressions, and operator expressions. For a full list of Rust expressions, see the Rust Language Reference.

Expressions in Rust are divided into two main categories: place expressions and value expressions. A place expression is an expression that represents a memory location. These expressions are paths which refer to local variables, static variables, dereferences (*expr), array indexing expressions (expr[expr]), field references (expr.f) and parenthesized place expressions. All other expressions are value expressions. A value expression is an expression that represents an actual value.

Block statements are always value expressions. When a block statement is evaluated, each statement within the block, except for item declaration statements, is executed sequentially. The final expression, if provided, is then executed. For example, the following code assigns a block expression to variables y and z.

Examples of Block Expressions
Output of Example

--

--