Rust Lang

Rust Language #

From Wikipedia;

Rust is a multi-paradigm, high-level, general-purpose programming language designed for performance and safety, especially safe concurrency. Rust is syntactically similar to C++, but can guarantee memory safety by using a borrow checker to validate references. Rust achieves memory safety without garbage collection, and reference counting is optional.

Rust was originally designed by Graydon Hoare at Mozilla Research[18], with contributions from Dave Herman, Brendan Eich, and others. The designers refined the language while writing the Servo experimental browser engine, and the Rust compiler. It has gained increasing use in industry, and Microsoft has been experimenting with the language for secure and safety-critical software components.

Rust has been voted the "most loved programming language" in the Stack Overflow Developer Survey every year since 2016, though only used by 7% of the respondents in the 2021 survey.

Note from ShyanJMC #

If you do not want (or if you can not) install Rust on your system for many reassons, you can use the official online interpreter; https://play.rust-lang.org/

History #

From Wikipedia;

The language grew out of a personal project begun in 2006 by Mozilla employee Graydon Hoare, who stated that the project was possibly named after the rust family of fungi. Mozilla began sponsoring the project in 2009 and announced it in 2010. The same year, work shifted from the initial compiler (written in OCaml) to the LLVM-based self-hosting compiler written in Rust. Named rustc, it successfully compiled itself in 2011.

The first numbered pre-alpha release of the Rust compiler occurred in January 2012. Rust 1.0, the first stable release, was released on May 15, 2015. Following 1.0, stable point releases are delivered every six weeks, while features are developed in nightly Rust with daily releases, then tested with beta releases that last six weeks. Every 2 to 3 years, a new Rust "Edition" is produced. This is to provide an easy reference point for changes due to the frequent nature of Rust's Train release schedule, as well as to provide a window to make breaking changes. Editions are largely compatible.

Along with conventional static typing, before version 0.4, Rust also supported typestates. The typestate system modeled assertions before and after program statements, through use of a special check statement. Discrepancies could be discovered at compile time, rather than at runtime, as might be the case with assertions in C or C++ code. The typestate concept was not unique to Rust, as it was first introduced in the language NIL. Typestates were removed because in practice they were little used, though the same functionality can be achieved by leveraging Rust's move semantics.

The object system style changed considerably within versions 0.2, 0.3, and 0.4 of Rust. Version 0.2 introduced classes for the first time, and version 0.3 added several features, including destructors and polymorphism through the use of interfaces. In Rust 0.4, traits were added as a means to provide inheritance; interfaces were unified with traits and removed as a separate feature. Classes were also removed and replaced by a combination of implementations and structured types.

Starting in Rust 0.9 and ending in Rust 0.11, Rust had two built-in pointer types: ~ and @, simplifying the core memory model. It reimplemented those pointer types in the standard library as Box and (the now removed) Gc.

In January 2014, before the first stable release, Rust 1.0, the editor-in-chief of Dr. Dobb's, Andrew Binstock, commented on Rust's chances of becoming a competitor to C++ and to the other up-and-coming languages D, Go, and Nim (then Nimrod). According to Binstock, while Rust was "widely viewed as a remarkably elegant language", adoption slowed because it repeatedly changed between versions.

Rust has a foreign function interface (FFI) that can be called from, e.g., C language, and can call C. While calling C++ has historically been challenging (from any language), Rust has a library, CXX, to allow calling to or from C++, and "CXX has zero or negligible overhead."

In August 2020, Mozilla laid off 250 of its 1,000 employees worldwide as part of a corporate restructuring caused by the long-term impact of the COVID-19 pandemic. Among those laid off were most of the Rust team, while the Servo team was completely disbanded. The event raised concerns about the future of Rust.

In the following week, the Rust Core Team acknowledged the severe impact of the layoffs and announced that plans for a Rust foundation were underway. The first goal of the foundation would be taking ownership of all trademarks and domain names, and also take financial responsibility for their costs.

On February 8, 2021 the formation of the Rust Foundation was officially announced by its five founding companies (AWS, Huawei, Google, Microsoft, and Mozilla).

On April 6, 2021, Google announced support for Rust within Android Open Source Project as an alternative to C/C++.

Components #

  • Cargo

Cargo is the package and dependency manager for Rust programms. Allow to download and install dependencies for your rust programs.

To create a new project;

cargo new [project_name]

To build the project;

cargo build [--release or not]

To build and run the project;

cargo run

To check the project;

cargo check
  • Rustfmt

Rust Format is the core program that takes rust program as input and then replace spaces and tabulations to formatted code in complience of Rust style guide.

  • Clippy

Clippy is the Rust’s built-in tool to improve the performance and readability of code. Clippy have more than 400 rules at 2021.

  • Rustc

Is the Rust’s compiler. You can set arguments by default to rustc setting the variable; RUSTFLAGS . My actual arguments to each execution of rustc are;

export RUSTFLAGS="-C target-feature=+crt-static -C target-cpu=native -C link-arg=-s"
  • Rustup

Is a toolchain (a set of tools) to install and use rust for differents targets (systems and architectures in which you will execute the program). Also is the best way to install and keep update rust, this is because is agnostic of operative system and do not need administration/root access to install rustc, cargo, etc-

To install rustup

curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh

To update rustup and your toolchain

rustup update

To uninstall

rustup self uninstall
Note: If you want use the cross compile, you must use by default the toolchain for your computer and the target must be the final cross compilation you want

Theory #

Rust as many others programming languages have;

  • Compiler

Is the component that takes the code and transform it to machine code (binary code, bits (0 and 1) with specific format) with a specific strcuture (is not just read and execute the instructions, must be a specific structure ) and information. With those structures and additional informations, the CPU can work in an specific way and execute properly what developper indicated.

  • Pre processor

Are options inside the code which indicate to the compiler some specific things to corroborate/do at compiling time.

  • Variable

An variable is something like an “container”, this is because can contains one type of information; number, strings or chars. Identify with a name [variable_name], the information stored in memory, so when you want access to that information you can do it using the variable’s name as identifier.

The variables in Rust can be created with;

let [variable_name] = [value];

or if you want specift the type;

let [variable_name]: [type] = [value];

In Rust if a variable is intentionally unused in specific conditions, you must prefix it with an underscore; _[variable]

  • Structure

Is a collection of variables under the same structure outside specific function. Is used to group variables with the same purpose.

  • Function

Is a piece of code that do an specific thing. Across many languages there are many ways to do it but in C,C++,Rust and others the way to do it is put the code between; { and }. The compiler also must know when a piece of code correspond to an function, so before the name must be the “fn” word.

For example;

fn [function_name] ( [arguments] ) -> [return_type] {
	[code];
	[code];
	[code];
}

The function can return to caller a value, for example; we execute some math operation and the return to caller is the result of it.

The function’s part in which you declare the “fn” keyword, and the function name as arguments and return’s types is called; signature.

  • Method

Sometimes called “objects”. Is similitar to functions but a method is used in the context of a struct, enum or trait). The methods have as first argument “self”. As functions, the methods takes the function’s return and works with it.

  • Arguments

An argument is information passed to the program or function to work with it.

You will see in the future that will be an argument called “&self” it means itself. Which is used when you create a method which is used to take another function’s returns.

  • ASCII / UTF

ASCII and UTF are character (numbers, letters and symbols) encoding standard for electronic communication. UTF have 1,7,8,16 and 32 bits variants. Those standards use a combination of bits to represent many symbols as is possible.

  • Instruction

An instruction is an order to execute. Can execute anything thay language supports.

But the true is this; each instruction that your code execute is really a function (or a macro).

  • Comparations

A comparation in Rust can be only done between values of the same type.

You can only compare variables, or values, of the same type. You can not compare a integer number with a string for example, just int with int, char with char and string with string. Yes, you should convert everything to ASCII table if you need compare but will provide you more challenges.

In Rust if you put in a comparation " _ " means “anything”.

  • Headers / Libraries

Every programming languge have header/libraries. They are collections of functions (and other informations) in one file that any developper can use in own code. For example; you can just use the standard library and you will not need to develop the information and standar to provide knowledge to rustc what is an int, char, etc.

  • Socket

A socket is a special type of thing. Without considering the operative system in which you execute the program.A socket allow a program to communicate with anothers programs transfering information, this type of communication can be in local (Unix domain sockets, in wich the program can communicate with others in the same computer that is running) or trough internet (TCP/IP, UDP and others). Yes, the internet is based in clients and servers which interact trough internet sockets in their operative systems.

  • Debug

The debugg is a process in which you take XX program and analyze how works (with the help of specific symbols inside the program) to find and fix issues. The process allow also to find ways for hacking and cracking it. Always release programs to others without debug symbols.

  • File extensions

Rust programs and libs have the “.rs” extension.

  • Namespaces and objects

A name is an alias for signs. A sign allow to identify a specific resource to specific object. If many objects have the same functions, calls and/or interfaces, the namespace can allow to track them to specific object.

Ok, a namespace track a thing to a specific object, but what is a object? In general and trying to include all paradigms; An object is a piece of thing which have an interface (the way in which interact with others objects). Inside the object there are many pieces of information which can or not depends of the external environment outside the object. That information inside the object can be properties and atributes.

That explenation works for programming, operative systems, data bases, networking, contianers, virtualization, etc. Each of them have specifics application of an object.

  • Array

An array is a collection of elements (all with the same type) under the same name, but each element have their own memory position.

  • Pointer

A pointer is it. A pointer to something (RAM information, over variables, etc). In Rust they are referred with “&”, they don’t have any special capabilities other than referring to data, and have no overhead.

  • Smart pointer

Unlike normal pointers, smart pointers expand they capabilities as data structures. Another difference is; “smart pointers” take the ownership of pointed data, normal pointers not.

“String"s and “Vec"s types are smart pointers.

  • Crate

In Rust, a crate will group related functionality together in a scope so the functionality is easy to share between multiple projects. This is what in another languajes are called “headers” and/or “libraries”.

In a Cargo project, the librarie crate is in; “src/lib.rs”

The difference is; In Rust even “main.rs” (the future binary) is a create.

In Rust you can find the central repository for crates in; https://crates.io/

  • Module

As a crate is group of functionality, the module is the code group. A module constains one or more functions.

So a crate can group many modules as developers want, and each module have related (in the objetive) functions. But there is a very important thing; a module can have two type of code; a private code (in which only the module or module’s function will can use it) or public (in which you can call it from your external code).

  • Iterator

“Iterating” is the action of process and work with each element in a series, and an iterator is just the way to do it.

In Rust the iterators are compiled in a much low level than others codes.

  • Concurrent execution Is when different parts of a program execute independently.

  • Parallel execution Is when different parts of a program execute at the same time. The main difference within concurrent execution is that the different parts despite execute at the same time, they depends of each other in some way.

  • Thread Is a piece of code that runs in concurrent or parallel execution.

  • Cross compile

A cross compile is a way to compile a binary from one computer with X CPU and Y system to another computer with N system and/or E/X cpu.

In cross compilations you must use the default toolchain for your system and then specify the target you want.

Cargo project’s structure #

When you create a new project with cargo (the recomended way to use rust projects) with “cargo new [project_name]” there are some things you need to know about;

  • You can integrate git with cargo adding argument; --vcs=git
  • The “Cargo.toml” file is the project’s configuration file which constains the project’s name, version, edition and the same information for all dependencies.
    • Cargo.toml’s sections:

“[profile.XXXXXX]”:

Here you customize “dev” and “release” specifying compilation flags under “[profile.XXXXX]” sections.

“[package]”:

Here you can use keys like; “name” (for package’s name), “license” (for specify package’s license. Take under consideration that if you publish your libs in crates.io must be open source), “version”, “description”, “version”, etc.

“[dependencies]”:

Here you can specify which external crates you will use inside your code.

And others sections.

  • In src/ directory (or folder, is the same thing with differents name) you must put your program’s files and your headers / libs (crates). By default there is file; main.rs.
  • If just exist “main.rs”, the crate root will be it. If exist “lib.rs” the crate root file will be it.
  • In target/ directory you will find the executable program based in two things;
    • The target (CPU architecture and operative system); is not the same compile for AArch64 using x86 than compile for BSD using Linux, each compilation will have the respective folder. My reccomendation is always compile for your specific CPU if you will not distirbute the binary file.
    • If is a release or debug; just for internal use is reccomended use debug release, for release to public must be used “release” because with it will apply optimizations and the final executable will be smaller.

For complete understanding see online documentation;

https://doc.rust-lang.org/cargo/

Libraries #

You will can not do much without the respectives standard libraries. In Rust you can use libraries with; “use”. A library is a compilation of functions, structs and other code. All inside same file, so you can access many functions and features from another files:

use [library]::[specific_function_or_information];

If you do not specify the function or information to import from that library, the whole lib will be imported.

By default, as minimium, you will need:

use std::io;

At this point you know (with the above example) you have an lib called “std” (abrevation of “standard”) and you are importing from it a colection of features from “io”.

Variables #

As we said before, a variable is a container wich store specific type of information.

By default, rust compiler interpreter the information that will been assigned to variable and format the variable to that type. If you want specify the type of variable the syntax is this;

let [variable_name]: [type] ;

In Rust by default when a variable is created is inmutable (which means that will not change over time) so if you created the variable but then you need change the value, you have two options;

  • Shadow it; this means that you will declarate again the variable wich overwrite the old with a new value.
let var1 = 5;
[operations];
let var1 = 3;
  • Set as mutable; is not the best solution, but if you know that the variable’s value will change over time after “let” specify “mut”:
let mut [variable_name];

Types #

Ther are many values types which you can use in your code;

Numbers #

There are two numbers types in Rust; integers (know as numbers without fractional part. ), and numbers with decimals (know as “float”).

  • The size of this types (what is the higher value that can contains) depends of the number of bits to use for store and represent it.
  • The integer numbers are represented as “iX” and float numbers as “fX”. The letter “X” is the number of bits to use (8,16,32,64 and 128 bits are available), floats just can use 32 or 64 bits (at 2021 the speed in 64 bits is the same or higher than 32 bits, so use 64 bits).
let intvar1: i64 = 32000;
let floatvar2: f64 = 32.0001;

or if you do not specify the types you can just assign the value.

  • Both (int and float) can be signed (which means that can be positive or negative) or unsigned (which means that can be just positive). Integers and floats signed are reprented as “iX” and “fX” respectly. Unsgined ints are represented as “uX”.
  • Integers can be represented as decimals (by default), hexadecimal (0x[XXXXX]), octals (0o[XXX]), binaries (0b[XXXXX]) and as a byte (u8 only) (b’X’).

Numbers also have one good feature; tuples. A tuple is a general way of grouping together a number of values with a variety of types into one compound type. Tuples have a fixed length: once declared, they cannot grow or shrink in size.

let variable1: (i32,i64,i8) = (128,32000,233);

But if you try to impress in screen variable1 you will can not, that is because Rust compiler do not know what value are you referencing. Because of that Rust implements another good feature to assign a variable to each value of tuple:

let variable1: (i32,i64,i8) = (128,32000,123);
let (x,y,z) = variable1;

With above example, we create a tuple with one integer 32 bits type (the number 128), one integer 64 bits type (the number 32000) and one integer 8 bits type (the number 123). Then we create 3 new variables (x,y and z) in which we assign the values in order (x -> 128, y -> 32000 and z -> 123). This process is called; destructuring.

But also tuples have the advantage to index parts, so using dot (”.”) you can use specifics parts of a tuple. Remember, in langagues (or most of them) the tuples, arrays and lists starts in zero (because is based in the position change number);

let variable1: (i32,i64,i8) = (128,32000,123);
let x = variable1.0;

With abose X variable will have value 128. If the number was 1, X had 32000.

Symbols #

Symbols can be single letters (know as “char”) and a strig of characters (know as “string”). Technically and internally Rust calls letters as “grapheme clusters”.

  • When is a character, the same must be inside single quotation marks ‘X’.
  • When is a string, the same must be inside double quotation marks “XXXX”.
  • The strings are represented as “str” in their most primitive way and “String” as the most modern, the difference is a “str” type is inmutable sequence of UTF-8 bytes and “String” is a mutable sequence. Both are dynamic (this means that you can assign directly without considering the input leght ). So you must use “str” when you use an input string which will not change over time and you must use “String” when you need store an input string which will/can change over time.

Note; instead of many other langs, in Rust you can not index words in a String.

The differences between “str” and “String” is;

  1. String is growable, can grow over time.
  2. String is mutable, “str” not.
  3. String is owned, see “Ownership” chapter.
  4. String support “UTF-8” encoding, which since 2012 you want it. So, not always a variable’s lenght in memory will be equal at words numbers, because of that (about UTF-8 encoding and how store letters and symbols in memory) you can not index words in a string variable. Rust provides different ways of interpreting the raw string data that computers store so that each program can choose the interpretation it needs, no matter what human language the data is in, so be carefull if you store data in a string variable as bytes, scalar values or graphene clusters because you must read in the same way to read properly the data.

Some of the functions in Strings are;

  1. “String::new()” : Create a new empty string variable.
  2. “String::from(XX)” : Create a new string variable from XX.
  3. “.push_str(XX)” : Append “XX” to an existing string variable.
  4. V1 + V2 : You can append V2 string variable to the V1’s string variable end. But “V1” and “V2” will not be longer under score if not used by reference, see “Ownership” chapter. This operator use the “add” method, but use “str” as V2, so be carefull because “str” and “String” are not the same and can cause problems.
  5. “format!(”{} {} {} {}", V1,V2,V3,VN) : Is the same that “+”. Works equals than “println!” but instead print in stdout, store it in one variable as returns. The return’s type is String. One thing about this option not take the ownership.
  6. “.to_string” : Convert a string to string format value.
  • Booleans; true or false.

While tuples can be only intergers, list of array types can be of any types but just one type for all elements.

let list1 = [1,2,3,4,5,6];
let list2 = ['a','b','c','d','e','f'];
let list3 = ["Hello","World","Rust","rules!"];

At the rest, tuples and arrays lists works in the same way. As the above example; remember, chars go with simples quotation marks and strings with double.

But what about if you have a tuple with 35 equals types? You can specify it with 2 declarations;

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

As you can see there are many differences; instead () for type, you specify with [], the number of that type is separeted with “;” and the list go with [] instead of (). And what about if you have 345 values of the same type? Well, same as before but now with values instead of

let array5 = [34; 320000];

With above example, array5 is a variable with 320000 values. Each values of those 320000 have “34” as value.

Each value inside a variable is indexed in the variable, in the same way as tuples. First value (left to right) have position zero (0), the next have position one (1), etc. But in this case, the access is not with “.” (dot), is with “[X]”:

let array6 = ["Hello"," ","World"," ","How"," ","are"," ","you","?"];
let variable7 = array6[0];

With the above example, we have a variable called “array6” which have 10 positions (from 0 to 9. Remember, arrays and tuples start from zero). As “variable7” is taked the value of array6’s position zero, the value is “Hello”, if the array was “1” so the value is " " (space), and if was “9” so the value is “?”.

Pointers #

As we said in the Theory section, a pointer is that; a pointer to something. That “something” can be anything that exist; some information into RAM, other variables, etc.

For example;

fn main() {
    let information = "Hello World".to_string();
let variable1 = &information;
println!("{}",variable1);

}

The above code will create a variable called “information” which contains the string “Hello World” passed as it properly by “.to_string()” function. This last allow a string to be printed in screen.

Then the variable “variable1” is a pointer; because is doing reference to “information” RAM memory with the symbol; &

So “variable1” will be poiting to “information”. When the second change, the first too.

In the above code “variable1” poting to “information” is taking as information to “Hello World”.

There are many ways to use pointers, but it is the simplest.

Slice #

Is more used in functions’ returns, the slice is specified with; “?” and means that you do not know exactly what you will return. Is very usefull when you get some general information (like a String) and you do not know what specifically you will return.

Example;

fn function1() -> ? {
	[code];
	[return_known]
}

Structs #

A struct, or structure is a type which allows you to group variables inside one logical domain. Instead like tuples, in structures you need declarate the variables which will contains the values.

Each variable inside the struct is called field. A structure without files is called unit-like struct

Syntax;

struct [struct_name] {
	[variable1]: [type],
	[variable2]: [type],
	[variableN]: [type],
}

To access to modify or read the values, first you need to create a variable. That variable will have the ownership of that struct, and trough that variable you will access to the structure. When you create that variable, you must not assign a type, as value must be [struct_name] and then you will specify trough { …} the each component’s value.

But if that variable was declareted before you can use this syntax to read/write (if that variable is mutable, of course); [variable_name].[struct_component]

For example;

struct User{
    email: String,
    age: u32,
}

fn main() {
    let mut var1_structure = User{
        email: String::from("shyanjm_protonmail.com"),
        age: 0,
    };
    var1_structure.age = 43;
    println!("Struct User from instance var1_structure: {} {}", var1_structure.email, var1_structure.age);
}

As you can see, there are a few differences when using structs:

  • The values are assigned with : not with equal =.
  • When you initialize the struct, you must init all variables, even if you will be provide them the right value before.
  • Using the dot . you can access the data without problems if the variable was declarated before (even if are others structs).
  • You can not impress directly a struct, this is because Rust will not known how to formatt it.

And if you want return a struct, just put the struct’s name as return type. But you can not pass a struct directly, you must pass the compoents.

If you integrate a struct into a function, and if the argument’s name are exactly the same than the fields you can just put the name and Rust will assign directly the value (this is called; Init shortland). But in my opinion, is just a word, so put the names because is one the best practices you can do; make your code easy to read for others without matter the expertise level.

Another time-saving thing that you can do is assign, with a copy operation, the rest of variables from struct A to struct B. As I said before, in my opinion is best writte more but make the code more easy to read and understand than save a few lines of code, but here the syntax;

let user2 = User {
	email: String::from("another@example.com"),
	..user1
};

That code will create an instance from User struct initing the email field and doing a MOVE operation from the rest user1’s fields so they are not longer available in user1 struct. For this part go to “Ownership” section.

Also as with structs you can create customs types using the primitives (integers, floats, etc) but remember, do not assign the struct as type, the struct assigned as value is the type self. With it as the same way we saw before you can group the types in tuples, the syntax is;

struct [struct_name]([type],..,..);

for example;

struct Position3d(i64,i64,i64);

let position1 = Position3d(64,32,0);

There are some points about struct tuples that you need have in consideration when you work with they;

  • A struct tuple can not be passed as argument/parameter to another.
  • You can still access to each part of the tuple normally as any tuple with dot . or as array.

Functions #

The first function you will see is “main”.

fn main(){
	[code];
}

That function is where your program start. An function will not be execute unless is called inside “main”.

If you come from C,C++ and others you know that every function after “main” will produce an error when try to compile and because of that you must use prototypes to fix it. In Rust this is not necesarry, you can put every function you want after “main”.

You will see that inside “main” function there is “println!”. The “!” indicate that is not a function, is a macro (we will see in the future).

Functions have this syntax;

fn [function_name]( [arguments] ) -> [Return_type] {
	[code];
	[code];
	...
}

With above example;

  • [function_name]

    This is the function’s name, you use it to know how to call it and where is called.

  • [arguments]

    Arguments are the options that you pass to the program.

  • [Return_type]

    A function can return to caller a value. This value can be anything recognized as a type in Rust .

Inside Rust the return is specified into the function’s code with word “return” or with just puting a variable or value. So that value will be returned to the variable or function who called it. Instead of the rest of the code, the return goes without the semicolon “;” at the end of line.

From StackOverflow;

You can omit the return keyword only if the returned value is the last expression in the function block, otherwise you need to explicitly use return

If the argument is specified like this;

&[String]

Means that the argument is a array of strings, you can access directly as an array.

There are a list of common functions and macros.

println! #

Is used to impress in screen. The syntax is;

println!(" [text] {}", variable_X);

The [text] is the string text you want to impress in screen.

The “{}” indicate to Rust that there must be the “variable_x” value, to in execution time will replace {} with the value of variable_x. You can add many {} and variables as you want.

“Println!” print the message to stdout.

eprintln! #

Works in the same way than “println!” but prints the message to stderr. Which is used to print only issues.

String #

This library allocated in standard rust returns an string.

There are many ways to store and use strings into variables.

  • To create an empty string into a variable:
let [variable_name] = String::new();
  • To store an string into a variable:
let [variable_name] = "[TEXT]".to_string();

or

let [variable_name] = String::from("[TEXT]");

or even

let [variable_name]: String = "[TEXT]".into();

This last line “into” will transform [TEXT] into String, because is the text type.

  • To append a string into another string variable:
[variable_name].push_str([TEXT_OR_STRING_VARIABLE]);

or

[variable_1] + [variable_2]

or

[variable_to_store] = format!(".... {} {} {} {} ...", var1, var2, var3, varn);
  • To convert from string to char:
[string_variable].chars()

is a good choice use this with an iteration.

  • To iterate over each letter:
for X in [string_variable].chars() {
    .....
}

.to_lowercase() #

This function do what name indicate, transform some String into equivalent to lowercase.

The syntax is;

[str_or_string_variable].to_lowercase()

.contains(XXXX) #

This method/function takes the input and process if have “XXXX”. The input can be “str”, String, char, or slice of chars.

Returns “True” as ‘()’ if is right or “False” if nothing was found.

The syntax is;

let strings = "hello world".to_string();
let return1 = assert!(strings.contains("world"));

.lines() #

This method/function takes the imput and create an iteration over each line (you can set the newline in a string with “\n”). You can go to the next line with “.next()” and if you go the end of line the return will be “None”.

The syntax is;

let text = "foo\r\nbar\n\nbaz\n";
let mut lines = text.lines();

assert_eq!(Some("foo"), lines.next());
assert_eq!(Some("bar"), lines.next());
assert_eq!(Some(""), lines.next());
assert_eq!(Some("baz"), lines.next());

assert_eq!(None, lines.next());

io::stdin() #

As you can see in the name, the function is “stdin” from the lib “io”. This function takes the input and process it inside a struct (in a global buffer), so can be handled properly into a variable.

The syntax is;

let [mut_or_not] [variable_name] = io::stdin();

or if you have a previously mutable variable formatted as a string type:

let [mut_or_not] [variable2_name] = stdin.read_line(&mut [variable_name])?;

We need create a new variable because “stdin” has a return value that must be stored in someplace, unless you use “expect”. Because the imput is stored inside a global buffer, you can force the lock of that buffer so your thread is the only who can access and read/write on it. With this you use a explicit synchorinization;

let stdin = io::stdin();
let mut [var1_name] = stdin.lock();
[var1_name].read_line(&mut [variable_name])?;

Expect() #

This is an special object. Is used if you want print on screen something when the previous function goes to an error.

[previous_function_or_variable].expect("[TEXT]");

With above example if for some reason the first function goes to error will be passes to “expect” and will impress [TEXT].

Have in consideration this; “expect” takes “str” as input, so you can not pass a string directly, you need convert it first. That “str” return of the previous function,object,variable or method is an “Err” return. Take it; is not a normal return, the type is “Err”.

read_line( [variable] ) and read_line_prompt(msg: &str) #

This object works with “stdin()” because reads a line (pay attention to it, a line) and store it in [variable] which must be a variable formatted as string.

stdin().read_line(&mut [variable]);

You can use the variation; read_line_prompt(msg: &str).

That function shows to user the string passes as argument ( the text beetwen double quotas ) before call to read_line to store the input.

parse() #

As many functions, this is one of the most helpfull objects in Rust.

Syntax;

[variable_target]: [new_type] = [variable1].[another_object_or_not].parse()

Parse will transform a string slice from [variable1] into another type, generally into the variable’s target type.

I’m sure to say that you will use this method a lot.

trim() #

This is a very helpfull object.

Syntax;

[variable].[trim_function];
  • “trim()” removes the leading and trailing whitespaces and tabulations in a string.

  • “trim_start()” only removes the leading whitespaces and tabulations in a string.

    • note

      trim_left was deprecated and replaced with “trim_start”.

  • “trim_end()” only removes trailing whitespaces and tabulations in a string.

    • note

      trim_right was deprecated and replaced with “trim_end”.

  • “trim_matches(’ ’)” removes the pattern from leading and trailing possitions in a string.

  • “trim_start_matches(’ ’)” removes the pattern from leading possitions in a string.

  • “trim_end_matches(’ ’)” removes the pattern from trailing possitions in a string.

strip #

Strips from strings the [XXXX] pattern. Obviusly the return is an string to passed variable.

Syntax;

[variable].[strip_function];
  • “strip_prefix("[XXXX]”) " removes the pattern [XXXXX] from leading possitions in a string.
  • “strip_suffix("[XXXX]”) " removes the pattern [XXXXX] from trailing possitions in a string.

is_ascii() #

Checks if the string has ASCII values. Returns a boolean type.

Syntax;

[variable].is_ascii();

len() #

Returns the numbers of bytes of variable size. Pay attention; “numbers of bytes”, NOT “number of chars/etc” (yes, in ASCII table each value use 1 byte but it will not be like this for ever). Returns interger.

Syntax;

[variable].len();

is_empty() #

Check if the variable has not information inside. Returns a boolean type.

Syntax;

[variable].is_empty();

as_bytes() #

Transforms each character into byte equivalent. Returns integer type.

Syntax;

[variable].as_bytes();
[variable].as_bytes_mut();

Assert #

Assert is a macros family to compare one or two things. The only common behaviour is they found that do not match with expected result, will call panic! and finish the program.

This apply not only for common variables and types, also for returns.

  • To check if something’s returns is ‘True’;
assert!([thing], [message_to_show_if_panic], [variables_if_panic_message_have_variables]);
  • To check if two things are equal;
assert_eq!([thing_1],[thing_2],[message_to_show_if_panic], [variables_if_panic_message_have_variables]);

“[thing_1]” is called by rust “left” and “[thing_2]” is called by rust “right”.

  • To check if two things are not equal;
assert_ne!([thing_1],[thing_2],[message_to_show_if_panic], [variables_if_panic_message_have_variables]);

“[thing_1]” is called by rust “left” and “[thing_2]” is called by rust “right”.

Contains #

Contains functions returns true if XXX is in the value.

There are many implementations and crates to try under scope depending the scenario and the target variable;

  1. std::slice::contains

  2. std:str::contains

  3. std::option::Option::contains

Etc.

.clone() #

Some are not implicitly copied. Clone method implements the properly behaviors to copy values between variables without issues.

For example;

let var1= String::from("Hello World");
let var2= var1.clone();

is_ok() #

This method checks if some type’s Results is “Ok(X)”. Returns “true” or “false”.

For example;

let var1: Result<&str,&str> = Ok("Everything is OK");
assert_eq!(var1.is_ok(), true );

Control Flow; if, else, else if, loop, while and for #

There are many ways to control the program’s flow, all those ways are with a conditional comparation; if this is [something] to this other, do this, if not do this.

if, else and else if #

“if” statement is what you think. If the condition is right do the code inside “{}” if not continue. This statement can work with numbers, strings and any rust standard type.

The condition can be within “()” but probably rust’s compiler will tell you to delete them, this is because will try that you avoid it if they are not needed.

For example to comparate two numbers;

fn main() {
    let var1 = 45;
    let var2 = 300;
    if var1 < var2{
        println!("{} is lower than {}",var1,var2);
    }
}

The above code will test if “var1” (with value 45) is lower than “var2” (with value 300), if that condition is true will execute the code whitin “{}”, in this case print in screen.

The “if” statement can not just work with numbers, as I said before; can works with any rust standard type.

In the condition you can use any function, even their returns;

fn main() {
    let var1 = "Hello World";
    if var1.is_empty() != true {
        println!("{} is not empty",var1);
    }
}

The above code will create a variable with string “Hello World”, then will test if “var1” is empty.

If the if’s return is “true” will not execute something, but if is not (that means “!=”) true will execute the code whithin “{}”.

But, what if you want do a test and take more than one action considering the condition’s returns? Easy;

if [condition] {
	[code_to_execute_if_condition_true];
}
else if [second_condition]{
	[code_to_execute_if_the_first_condition_was_false_but_this_second_is_true];
}
else {
	[code_to_execute_if_all_conditions_are_false];
}

You can use as many “else if” you need. As you can think, all condition’s returns are bool type (True or False).

Here some of if, while, for and else operations (remember that you can use normal opterations, not just aritmetic);

  • a < b

If a is lower than b.

  • a > b

If a is granten than b.

  • a == b

If a is equal to b. Do not confuse with a single equal; “=” which is an assigner, not a comparation.

  • a

Check if a is equal to “True” (the boolean value).

  • !a

Check if a is equal to “False” (the boolean value).

fn main() {
    let var1 = "Hello World";
    if !(var1.is_empty()) {
        println!("{} is not empty",var1);
    }
}

The above code is the same than before, but replacing " != True" with the smallest way.

As “if” is an expression, you can use it to control returns;

fn main() {
    let var1 = "Hello World";
    let vreturn1 = if !(var1.is_empty()) {
       5
    } else {
        1
    };
}

With above code, vreturn1 variable will have value 5 if the return of evaluation; var1.is_empty() is false, otherwise will have value 1. If you use “if”, “else if” and/or “else” to assign values as returns to variables, remember you MUST use the same values’ types in each arm of “if”, “else if” and “else” expressions, so consider it when you create your programs.

Repetition with loops; loop, for and while #

  • Loop

‘Loop’ tells to rust to execute something until that process is stopped explicitly.

Syntax;

fn main() {
    let mut x: i64 = 10;
    loop {
        println!("this is a loop");
        x = x +1;
	if x == 13 {
		break;
	}
    }
}

With above command rust create a variable called “x” of type; interger 64 bits, assigned with value “10”. Then execute the flow in “loop” printing “this is a loop” and adds one to it (because of that the “x” variable have “mut” word). Then evaluate if “x” is equal to 13, if is break the loop using the function “break”.

If you specify an argument in “break” will works as return value, for example;

[code];
break x +1;
}

If you need create/use a loop with conditional access, there are better options like; while and for.

  • While

‘While’ use (as the name sugest) a conditional test to know if execute the loop code or not.

Syntax;

while [condition] {
	[code_to_execute_if_condition_works];
}

For example;

fn main() {
    let mut x: i64 = 10;

    while x < 13 {
        x += 1;
        println!("{}",x);
    }
    println!("Ended while loop");
}

The above code will create a mutable variable of interger 64 bits type called “x” with value 10. Then will execute the code inside while block if the condition ( x < 13 ) is true, adding one to x and then printing the value. When the loop finish will print “Ended while loop”.

While is good when you do not need works with variable values in fixed sizes (like use variables in arrays and tuples), but if you need it the better option is “for”.

  • For

“For” is a special case, this is because works different depending of the language (“for” in C,C++ is not same in Rust, Python, etc). Rust’s “for” iterate in someting (commonly; a variable) and in each interation will execute the code inside “{}”.

Syntax;

for [new_variable] for [variable_with_information] {
	[code_to_execute];
}

For example;

fn main() {
    let var1 = [1,2,3,4,5,6];
    for iteration1 in var1 {
        println!("{}",iteration1);
    }
}

The above code will create an inmutable variable with an array of six values; 1,2,3,4,5 and 6.

Then will execute the “for” loop; will create a new variable called “iteration1”. That new variable (“iteration1”) will have each value of “var1” in order; will take “1” as value and then will execute the code inside “{}”, then will take as value “2” and will execute again the code, and it to finish with the last value (in this case “6”) and the execution of code. Take in consideration that can be or not in order the information retrived, but if you use the “rev” family functions and macros (for example, there are a lot more ) the information retrived will be different.

First code #

Now with your knowledge about Rust, we will do a program to convert Celcius to Fahrenheit;

As 1°C are 33.8° F the convertion is simple;

use std::io;

fn f_t_c_fn(user_input: String){
    let mut user_input2: String = String::new();
    let f_t_c: f64 = 33.8;
    let mut is_number: f64;

    println!("Insert number to convert;");
    io::stdin().read_line(&mut user_input2).expect("Error taking imput.");
    is_number = user_input2.trim().parse().expect("Error. Is not a number.");

    if (user_input.trim() == "C".to_string() || user_input.trim() == "Celsius".to_string()){
        println!("{} to Fahrenheit; {}",is_number, is_number + f_t_c);
    }

    else if (user_input.trim() == "F".to_string() || user_input.trim() == "Fahrenheit".to_string()){
        println!("{} to Celsius; {}",is_number, is_number - f_t_c);
    }

    else {
        println!("Valid options were not selected.");
    }
}

fn main() {
    let mut user_input = String::new();

    println!("Convert from Celsius to Fahrenheit (C/Celsius), or from Fahrenheit to Celsius (F/Fahrenheit)?");
    io::stdin().read_line(&mut user_input).expect("Error taking input.");

    f_t_c_fn(user_input);
}

The above program is simple but uses all knowledge acquired to now. First import the create; InputOutput from standard library.

Then create a function called “f_t_c_fn” which takes as argument “user_input” with “String” type (I use the same argument’s name as the variable that I pass to the function, but is not neccessary).

In the “f_t_c_fn” function, create an mutable variable called “user_input2” with “string” type assigning as value an empty string. Then create two float 64bits variables called “f_t_c” and “is_number”.

Ask the user to imput values reading line from standard imput/otput, saving the values in the mutable variable “user_input2”. If the imput values fails, show the error “Error taking imput.”.

The good code and objects use is here; the “user_input2” will passed to “trim” object, the result will passed to “parse” object, the result of it will passed to “is_number” variable. Remember, that variable is 64bits float, so if the convertion (by “parse”) was done properly, the result is assigned to “is_number”, if there is an error with the convertion is passed to “expect”.

Then start the comparation, and in my opinion take this carefully (not because you must read this with care, because you must understand why I wrotte it) and with very attention. We start a comparation with “if” and we include all the comparations inside “()” because we are not just doing one comparation; we are doing two comparations). First we delete all possible spaces at the start and end of “user_input” to check if is equal (remember, two ==, one = is for assign values) to “C”, but “C” or werever other alphabetycal value can not be compared directly with an string so after the string we pass it to “.to_string” call object to do the convertion properly. Then is specified the “or” exclusive condtion with “||” (“and” is “&&") to check the same but with “Celsius” string.

Take in consideration this about “or” and “and” conditionals in “if”/“else if”; when you use “or” || with two or more conditionals, is enogh that just one be true to execute the code inside if’s “{}”. When you use “and” && with two or more conditionals, all of them MUST be true in each one to execute the code.

Then, if the conditionals are true show in screen two variables; first user_input and then “user_input +/- f_t_C” depending of user’s selection.

So when the program execute, takes the user input and pass it to “f_t_C_fn” function and do the properly operations.

Ownership #

RAM management #

Take the RAM as an stack;

|--------------|
|    Value 5   |
|--------------|
|    Value 4   |
|--------------|
|    Value 3   |
|--------------|
|    Value 2   |
|--------------|
|    Value 1   |
|--------------|

The stack size depends a lot things; the OS, how the CPU configure the RAM’s frames, the CPU’s architecture, etc.

The sstack stores values in the way; “first in, last out” or “last in, first out”. So the first value “Value 1” is below “Value 2” and so on, if you want retrive a value from stack, you must start from upper to lower, so if you want the “Value 3” you must frist move 4 and 5 to another place before take 3.

Adding values to the stack is called “push” and retrive is called “pop”.

The “push” action store values in stack’s top, and the “pop” action retrive values from stack’s top.

In Rust’s webpage is explained in a perfect way;

All data stored on the stack must have a known, fixed size. Data with an unknown size at compile time or a size that might change must be stored on the heap instead. The heap is less organized: when you put data on the heap, you request a certain amount of space. The memory allocator finds an empty spot in the heap that is big enough, marks it as being in use, and returns a pointer, which is the address of that location. This process is called allocating on the heap and is sometimes abbreviated as just allocating. Pushing values onto the stack is not considered allocating. Because the pointer is a known, fixed size, you can store the pointer on the stack, but when you want the actual data, you must follow the pointer.

Pushing to the stack is faster than allocating on the heap because the allocator never has to search for a place to store new data; that location is always at the top of the stack. Comparatively, allocating space on the heap requires more work, because the allocator must first find a big enough space to hold the data and then perform bookkeeping to prepare for the next allocation.

Accessing data in the heap is slower than accessing data on the stack because you have to follow a pointer to get there. Contemporary processors are faster if they jump around less in memory.

When your code calls a function, the values passed into the function (including, potentially, pointers to data on the heap) and the function’s local variables get pushed onto the stack. When the function is over, those values get popped off the stack.

Keeping track of what parts of code are using what data on the heap, minimizing the amount of duplicate data on the heap, and cleaning up unused data on the heap so you don’t run out of space are all problems that ownership addresses. Once you understand ownership, you won’t need to think about the stack and the heap very often, but knowing that managing heap data is why ownership exists can help explain why it works the way it does.

Ownership rules #

In Rust there are three rules about the ownership;

Each value in Rust has a variable that’s called its owner.
There can only be one owner at a time.
When the owner goes out of scope, the value will be dropped.

Memory #

In all programming languages, the data stored and managed by the programm must be in memory. Here there are two possible options;

  • The programmer is the responsible to request memory and free the unused memory.

  • The language have a garbare collector (GC) which analyze the program’s memory and release the unsued memory.

Well Rust is more like the first, the programmer is the responsible to request data allocation in memory and release when it is not used. In Rust as many other languages the more common way to do it is using functions. But, generally, all programming languages works in the same way when is about stack and heap.

Rust as other languages, have a method to control the information. Supose you have this code;

fn function1(){
	let mut var1 = String::from("Hello ");
	var1.push_str(" World.");
}

fn function2(){
	let mut var1 = String::from("How are you?");
	println!("{}",var1);
}

In above code we have a variable called “var1” one in function1 and the other in function2, when (in function1) the code block ends, the memory allocated in stack to storage “Hello World.” is relased and cleaned, so when “function2” create a variable with the same name and different value in stack, the other information will not crash with this new one.

Rust execute a call ‘drop’ when a code block ends.

This is the more basic thing about ownership in Rust; each variable and value is valid (and stored in RAM) until the code block ends. But it will not work if you are using an external variable (like a normal external variable, functions’ arguments or struct for example). So remember; modulate your code in functions as you need do differents things, only pass data into one function using structs, functions’ arguments or external variables ONLY and ONLY if you really need that data and DO NOT return data outside the function if you REALLY do not need it, and also if you have a variable “x” which contains information to only “y” function, do not create that variable at “main” function (a habit from C or C++ from Universities generally), create it inside that “y” function so the Rust’s ownership can work properly. Variable scope

Let us say we have this piece of code:

let var1 = String::from("Hello World.");
let var2 = var1;

We have a variable called “var1” which contains in memory the value; “Hello World.” but, unless you maybe are thinking, the variable have not that value directly into stack. In var1’s stack is only the information about a poiter (which points, as the name said, to the heap location where the complete information is stored, in this case “Hello World."), the size integer (which contains in bytes the size of data allocated into the heap) and the capacity integer (which contains in bytes the max size that the variable can store into the heap using the pointer).

Variable scope and memory management #

Let us say we have this piece of code:

let var1 = String::from("Hello World.");
let var2 = var1;

We have a variable called “var1” which contains in memory the value; “Hello World.” but, unless you maybe are thinking, the variable have not that value directly into stack. In var1’s stack is only the information about a poiter (which points, as the name said, to the heap location where the complete information is stored, in this case “Hello World."), the size integer (which contains in bytes the size of data allocated into the heap) and the capacity integer (which contains in bytes the max size that the variable can store into the heap using the pointer).