Rust Programming Language

Learning

To learn I am using different resources including

  • Rust Doc

Hello World

From Rust Doc::Hello World

Hello World using rustc

We call println!(), which is a macro and NOT a function. We write this inside the main() function, and we define functions using fn keyword.\\ To compile we use rustc <filename.rs>. After compiling we will get a binary with same name as filename but without any extension(in Linux) and with .exe in windows.

fn main() {
    println!("Hello World!");
}
Hello World!

By default rust binaries are really big because it statically links libraries, to force it to link libraries dynamically add -C prefer-dynamic to the rustc command.

Hello World using cargo

Cargo is our package handler. To create a cargo project will use cargo new hello_cargo, this will create hello_cargo directory with the following structure.

hello_cargo/
├── Cargo.toml
└── src
    └── main.rs

main.rs is where our code will go(for now). This also initializes a git repository inside hello_cargo directory.\\ cargo.toml is our project's configuration file. Here we will add different dependencies etc. Now write code to display in main.rs. To build cargo project we can will use cargo build. This will create a binary(with lots of things beside it) inside a target directory. To simply run the program, use cargo run, for error checking use cargo check.

Common Concepts   control_flow variable functions

From: Rust Doc::Common Concepts

Variables and Mutable

First to define variables we use let keyword. For example:

fn main(){
    let var=5;
    println!("The value of var is {}",var);
}
The value of var is 5

By default variables are immutable,i.e. cannot be changed. For example code given below wont work

fn main(){
    let var=5;
    println!("The value of var is {}",var);
    var = 6;
}
error: Could not compile `cargouqGIqJ`.

and will result in error.\\ To deal with this we use mut keyword. Like

fn main(){
    let mut var=5;
    println!("The value of var is {}",var);
    var = 6;
    println!("The value of var now is {}",var);

}
The value of var is 5
The value of var now is 6

Value to a immutable can be assigned only once, either in declaration, or in assignment.

The variable we are referring to depends on the scope of the object. Scope define lifespan as well as where and how variable can be accessed. In a program same named variables can exist inside same program in different scopes.

fn main(){
    let var=5;
    let var2=45;
    println!("The value of outer var is {} and var2 is {}",var,var2);
    {
        let var = 10;
        println!("The value of inner var is {}! but value of var2 is still {}",var,var2);
    }
    println!{"The value of outer var is still {} and value of var2 is {}",var,var2};
}
The value of outer var is 5 and var2 is 45
The value of inner var is 10! but value of var2 is still 45
The value of outer var is still 5 and value of var2 is 45

Even without mut we can still change the value as well as type of variable by redefining it. For example

fn main(){
    let var=5;
    println!("Value of var is {}", var);
    let var = "hello!!!";
    println!("Now value of var is {}", var);
}
Value of var is 5
Now value of var is hello!!!

Constants

cost keyword is used to define constants. These are like let but with some differences.

  • always immutable

  • data type MUST be annotated

  • can be set during compile time or before only

Data types in Rust

Every "value" in Rust has a certain data type. Rust is a statically typed language, so it must know type of every value and variable at compile time, either implicitly or explicitly. Rust has different types of data types:

Scalar Data Type

Scalar type represent single value. These divide in 4 types

  1. integer Numbers without fractional components. Signed integer contain negative numbers, unsigned do not. Types of integer data types.

    LengthSignedUnsigned
    8-biti8u8
    16-biti16u16
    32-biti32u32
    64-biti64u64
    128-biti128u128
    archisizeusize

    isize and usize depends on the architecture of the system. To show a specific type we can append type at the end like:

    fn main(){
        let var1:u32 = 300;
        let var2 = 300u8;
    }
    
    error: Could not compile `cargoVFL40k`.

    Above code wont compile because 300 is too big to be u8(will cause overflow without checks). We can also write integer literals in following forms too.

    Literal TypeExample
    Decimal98_100
    Hexadecimal0xff
    octal0o77
    Binary0b11011
    Byteb'A'

    For example

    fn main(){
        let var1 = 0o67;
        let var2 = 0xf4;
        let var3 = b'Z';
        println!("Decimal: {} {} {}", var1, var2, var3);
    }
    
    Decimal: 55 244 90
  2. floating point Floating points in rust are f32 (single-precision) and f64 (double-precision).

  3. boolean This contain true and false.

  4. characters Characters are defined using char keyword and in single quote ',

Numberic Operations

Numeric operations are possible on and using integer and floating point numbers.

fn main(){
    let ( a, b, c, d) = (5.56, 45.56, 75.65 , 98.56);
    println!("Sum {} Diff {} Mul Div {} Remainder {}", a+b, b-c, c / a, d % a);
}
Sum 51.120000000000005 Diff -30.090000000000003 Mul Div 13.606115107913672 Remainder 4.040000000000009

Compound Data Types

Multiple values in one type. Rust has two primitives

  1. Tuple

  2. Array

Functions in Rust

We define a function using fn keyword and following it with function name. For example

fn main() {
    function_name();
}

fn function_name() {
    println!("Hello Function!");
}
Hello Function!

Parameters

Functions can have parameter(s) and type must be specified.

fn main(){
    multi(45.5,84.69);
}
fn multi(a: f64,b: f64){

    println!("{}",a*b);
}
3853.395

Expression and expression blocks

{
    a+b;
    "returned"
}

This is a expression block, statement without ; is returned at last and before that processed normally.

Function With Return Value

We must specify return type to a function. We do that using ->. Like expression block it may be last value or can be explicitly stated using return statement.

fn main(){
    println!("{}",multi(45.5,84.69));
}
fn multi(a: f64,b: f64)->f64{
    a*b
    // or
    // return a*b;
}
3853.395

Control Flow in Rust

if and else

if is an expression in rust, making it much more powerful.

if condition {
    // something
} else if {
    // something else
} else {
    // something else but different
}

because it is an expression it can be used anywhere any normal expression can be used. But when used in left side of let, value from all the branches should be of same data type

loop

Loops code forever. break exits the loop. Value written just after break will be returned. continue continues skips execution of loop from that point and starts over.

loop{
//code
    if condition {
        break value_to_return;
    }
}

while

while condition {
    //code
}

for

Loops over a range

fn main(){
    let numbers= {1,7,5,7,10};
    for num in numbers {
        println!("{}",number);
    }
}

match

Matches with all the possible values of a variable.

match somevariable{
    possible_value1 => { code for it },
    possible_value2 => code, //{ are not required,
    _ => code // code for rest of the cases
}
  • if let is great way to deal with only one case. else can be used as _ in this case.

    if let possible_valuen = somevariable {
        // code in this case
    }
    

Structures in Rust

To define

struct stuct_name {
    key1 : data_type,
    key2 : data_type,
    //....
    }

To use

let var = stuct_name {
    key2 : data_value,
    key10 : data_value,
    keynth, // if variable name in which data is stored is same as key then it can be written directly
    //...
}
struct_name.key1 //gives access to the key

Trailing commas are allowed.

let var = stuct_name {
    key2 : data_value,
    ..another_struct //can also be used
    // except key2 all the key are assigned values from another_struct
    //...
}

Tuple struct can be used for storing data without defining key names.

stuct stuct_name(type1,type2,type3);
let var = struct_name(val1, val2, val3);

Methods in Rust

Methods are defined on struct, enum and trait.

impl struct_name {
    fn fn_name(&self,...){
        //code
    }
    fn fn_name2(&self,...){
        //code
    }
}

Here self is associated with struct (or enum or trait) itself. Methods can take ownership of self, borrow mutably or immutably.

  • Multiple impl blocks per structure is allowed.

  • Any function written inside is an associated function.

  • Having self as parameter is not essential.

Enums in Rust

Enum let us define one from a set of value.

enum EnumName {
    FirstOption,
    SecondOption,
    //...
}

Here it is needed that option should be an identifier. Now that identifier can have objects associated with it.

enum EnumName{
    FirstOption(val1, val2),
    SecondOption(val4,val5)
}

=Option<T>-

It is an Enum. It can be used when we do not know when some value will be available or not.

enum Option<T> {
    None,
    Some(T),
} // T is type

Error Handling in Rust

Generics in Rust

Generic Data Types in Rust

Function Definitions

fn_name<T>(v: T)->&T{
    // code
}

Struct and Enum definations

struct strt_name<T>{
    var1: T,
    var2: T,
    //...
}

In Enums

enum EnumName<T1,T2>{
    A(T1),
    B(T2),
    //...
}

Method Definitions

impl<T> struct_name<T>{
    fn method_name(&slef)->T{
        //code
    }
}

This T should be constrained.

Traits in Rust

Traits are methods defined of types that may share functionality. Same trait name can be used to define similar functionality for different types.

An example of trait with name TraitName is written below. It has one method method_name (can be multiple) which takes self as parameter and returns T.

pub trait TraitName{
    fn method_name(&self)->T{
        //code
    }
}

Now we will impl-iment this trait for a type. This is done by writing

impl TraitName for type_name{
    //code
}

Now we can use this trait for type_name.

Basically you define multiple functions in a trait and then implementing these functions for different types. All the functions in a trait must be implemented for a type.

Default Trait

Default trait is used to define default values for a type. It code inside the trait if not defined later for a specific type.

pub trait Default{
    fn fndefault()->Self{
        //code
    }
    fn fnanother()->Self;
}

Here only fndefault has a default code. fnanother needs to be explicitly defined for a type.

Traits as parameters

We can give a trait as a parameter to a function. This is called trait bound. Only types that implement the trait can be used as arguments. This is done by writing

fn fn_name(arg: &impl TraitName)->T{
    //code
}

And there is another way of writing this known as trait bound.

fn fn_name<T: TraitName>(arg: &T)->T{
    //code
}

Multiple traits can be used as bounds too with + sign.

fn fn_name<T: TraitName + TraitName2>(arg: &T)->T{
    //code
}

Finally we can also use where clause.

fn name<T1,T2>(arg1: &T1, arg2: &T2)->T1
where T1: TraitName, T2: TraitName2{
    //code
}

Returning Traits

We can also return a trait from a function. This is done by writing

fn fn_name()->impl TraitName{
    //code
}

Especially useful when we want to return something that implements a trait but we do not know(or want to know, for example iterators and closures) the type. Only a single type can be returned even if multiple types implement the trait. It is more to do with branching statements in rust(according to me).

Using Trait Bounds to Conditionally Implement Methods

We can use trait bounds to conditionally implement methods. This is done by writing

impl<T: TraitName> struct_name<T>{
    fn fn_name(&self)->T{
        //code
    }
}

Here the method will be implemented only if the type implements the trait(or traits). For example, we can implement a method for String type only if it implements Display trait.

impl<T: Display> String<T>{
    fn fn_name(&self)->T{
        //code
    }
}

Validating References with Lifetimes in Rust

Lifetimes are a type of generic. They are used to tell the compiler about the relationship between the references. This is done by writing

fn fn_name<'a>(arg: &'a str)->&'a str{
    //code
}

Rust libraries and frameworks

Backlinks