Sage-Code Laboratory
index<--

Composite Types

Some composite types have all members of the same type but have an internal structure that make them special. The structs and enums can have members of different data types and are more known as "records" in other languages. These are all composite types predefined in Rust:

Array

Arrays are data structures having fixed size elements. We enclose in square brackets the elements, separated by comma like: [1,2,3]. First element can be found like in C using zero based subscript a[0].

Example:

let a = [1,2,3]; // All elements are integers i64
let b = [0;10];  // This create b=[0,0,0,0,0,0,0,0,0,0]

Note: The notation [T;N] is used to initialize an array with T using N members. This eliminate the need to create large data literals to simple initialize an array.

Tuples

Tuple literal is enclosed in round brackets () like in Python. (1, 2, 3). A tuple can have characters and numbers mingled like: (1,'a',"bcd"). The tuples are immutable collections. Interesting about tuples is the way a member is found using dot notation instead of subscript with brackets.

Example:

//define a tuple of 3 char elements
let tuple = ('a','b','c');

//first method to extract elements
let a = tuple.0;  //extract first element
let b = tuple.1;  //extract second element
let c = tuple.2;  //extract third element

// decompose a tuple and create 3 new variables x,y,z
let (x, y, z) = tuple;

Structs

This is a composite data where every element has a name and a type. So it is like a record into a database table. Unlike tuple every element can be accessed by its name using dot notation not indexes. In the next example we show how to declare a complex type Point. The user defined types in Rust start with Capital letters by convention.

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let origin = Point { x: 0, y: 0 }; // origin: Point
    println!("The origin is at ({}, {})", origin.x, origin.y);
}

You can open this example live and run it: struct

Enumerations

The enumeration is created using enum keyword. It is very similar to a struct except that struct is data oriented while enum is based on symbols. The enumeration is created to somehow extend the language with new keywords. The enumeration elements can be used with symbol :: or can be imported in the current name space using the "use" keyword.

Example:

#[derive(PartialEq)]
enum Color {
  Green,
  Yellow,
  Blue,
}

//use the new elements like symbols
fn main() {
  // unused variables have prefix "_"
  let _unused_green  = Color::Green; 
  let _unused_yellow = Color::Yellow;

  // import the elements of Color
  use Color::{Blue};

  //Blue color is not available without prefix
  let your_favorite_color = Blue;

  if your_favorite_color == Blue {
    println!("Your are a sad person!");
  } 
}

This example is available on-line here: enumeration

Note:

Homework: Try this example and remove first line: #[derive(PartialEq)]

Strings

In Rust, a string is implemented as a collection of individual bytes (i.e. u8 values) arranged in a particular sequence to form a text. But String is more than just a simple byte array: It is a composite type because it consists of multiple parts.

One part of a String is a pointer to the memory where the string is located in the heap. This pointer is usually of type *const u8 or *mut u8, depending on whether the string is immutable or mutable, respectively.

Another part of a String is the length of the string - the number of bytes in the sequence. This allows Rust to keep track of how much memory is allocated for the string as well as provide methods for measuring and manipulating the string.

In addition to the pointer and the length, a String can also contain metadata about the string. This might include encoding information, flags, and other data. All of this information is combined into a single, cohesive data structure that represents a string in Rust.

Overall, a String in Rust is a composite type that contains multiple parts, including a pointer to the actual string data, the length of the string, and potentially other metadata.

String Literals

String literals are enclosed in double quotes like this: "abc". Strings are encoded UTF8 and not ASCII. That means one character occupy a variable number of bytes. Strings are not null terminated like in C and can contains several null characters inside.

Strings are of two kind: immutable and mutable. The default notation "abc" will create an immutable string. You can convert a non mutable string into a mutable string using function to_string(). You can convert a mutable string into immutable string using operator "&" in front of the string variable.

Example:

let hello = "Hello ".to_string(); // create a String
let world = "world!";             // create a &str
let hello_world = hello + world;  // CONCATENATE 

Warning: Previous example and some of the next examples are code fragments. To run such code you need to wrap it in a function main(). Rust do not run statements outside functions. Also declarations using let are not supported outside functions. That will be a global variable that must be "static" otherwise is not supported.

Large strings

Strings can be created on multiple lines using continuation character "\". If we do not then the new line and the indentation is included in the string and we may wish to avoid it. "\" will eliminate both end of lines and the spaces and will reduce them to one single space.

let welcome = "Welcome \
               to rust \
               the fastest language \
               in the world.";
println!(welcome); 
/* will print: 
"Welcome to rust the fastest 
language in the world" */

A new way to define strings in Rust is this notation from this example:

let s = String::from("initial content");

Notes:

A literal for example "hello" is not of type String. It is of type "&str" (that is a string slice). Therefore to make a proper string out of a literal you can use above notation: String::from("") that is better than using "".to_string() method.

Option Type

In Rust, Option is a type that can either contain a value or be empty. This is useful for representing situations where a value might not be present, such as when reading data from a file that might not exist.

There are two variants of Option: Some and None.

To create an Option, you can use the following syntax:

let my_option: Option = Some(42);

// or

let my_option: Option = None;

You can also use the following functions to create an Option:

Example

Here are some examples of how to use Option:

// Check if the Option contains a value
if let Some(value) = my_option {
println!("The value is {}", value);
} else {
println!("The value is not present");
}

// Get the value of the Option
let value = my_option.unwrap();

// Handle the case where the Option is empty
match my_option {
Some(value) => println!("The value is {}", value),
None => println!("The value is not present"),
}

Read next: Collections