Marcin Jahn | Dev Notebook
  • Home
  • Programming
  • Technologies
  • Projects
  • About
  • Home
  • Programming
  • Technologies
  • Projects
  • About
  • An icon of the Core section Core
    • Programs Execution
    • Stack and Heap
    • Asynchronous Programming
      • Overview
      • Event Queues
      • Fibers
      • Stackless Coroutines
  • An icon of the .NET section .NET
    • HTTPClient
    • Async
      • How Async Works
      • TAP Tips
    • Equality
    • Comparisons
    • Enumerables
    • Unit Tests
    • Generic Host
    • Logging
    • Configuration
    • Records
    • Nullability
    • Garbage Collector
    • IL and Allocations
    • gRPC
    • Source Generators
    • Platform Invoke
    • ASP.NET Core
      • Overview
      • Middleware
      • Razor Pages
      • Routing in Razor Pages
      • Web APIs
      • Filters
      • Identity
      • Validation
      • Tips
    • Entity Framework Core
      • Overview
      • Testing
      • Tips
  • An icon of the Angular section Angular
    • Overview
    • Components
    • Directives
    • Services and DI
    • Routing
    • Observables (RxJS)
    • Forms
    • Pipes
    • HTTP
    • Modules
    • NgRx
    • Angular Universal
    • Tips
    • Standalone Components
  • An icon of the JavaScript section JavaScript
    • OOP
    • JavaScript - The Weird Parts
    • JS Functions
    • ES Modules
    • Node.js
    • Axios Tips
    • TypeScript
      • TypeScript Environment Setup
      • TypeScript Tips
    • React
      • React Routing
      • MobX
    • Advanced Vue.js Features
  • An icon of the Rust section Rust
    • Overview
    • Cargo
    • Basics
    • Ownership
    • Structures
    • Enums
    • Organization
    • Collections
    • Error Handling
    • Generics
    • Traits
    • Lifetimes
    • Closures
    • Raw Pointers
    • Smart Pointers
    • Concurrency
    • Testing
    • Tips
  • An icon of the C/C++ section C/C++
    • Compilation
    • Structures
    • OOP in C
    • Pointers
    • Strings
    • Dynamic Memory
    • argc and argv Visualization
  • An icon of the GTK section GTK
    • Overview
    • GObject
    • GJS
  • An icon of the CSS section CSS
    • Responsive Design
    • CSS Tips
    • CSS Pixel
  • An icon of the Unity section Unity
    • Unity
  • An icon of the Functional Programming section Functional Programming
    • Fundamentals of Functional Programming
    • .NET Functional Features
    • Signatures
    • Function Composition
    • Error Handling
    • Partial Application
    • Modularity
    • Category Theory
      • Overview
      • Monoid
      • Other Magmas
      • Functors
  • An icon of the Algorithms section Algorithms
    • Big O Notation
    • Array
    • Linked List
    • Queue
    • Hash Table and Set
    • Tree
    • Sorting
    • Searching
  • An icon of the Architecture section Architecture
    • What is architecture?
    • Domain-Driven Design
    • ASP.NET Core Projects
  • An icon of the Core section Core
    • Programs Execution
    • Stack and Heap
    • Asynchronous Programming
      • Overview
      • Event Queues
      • Fibers
      • Stackless Coroutines
  • An icon of the .NET section .NET
    • HTTPClient
    • Async
      • How Async Works
      • TAP Tips
    • Equality
    • Comparisons
    • Enumerables
    • Unit Tests
    • Generic Host
    • Logging
    • Configuration
    • Records
    • Nullability
    • Garbage Collector
    • IL and Allocations
    • gRPC
    • Source Generators
    • Platform Invoke
    • ASP.NET Core
      • Overview
      • Middleware
      • Razor Pages
      • Routing in Razor Pages
      • Web APIs
      • Filters
      • Identity
      • Validation
      • Tips
    • Entity Framework Core
      • Overview
      • Testing
      • Tips
  • An icon of the Angular section Angular
    • Overview
    • Components
    • Directives
    • Services and DI
    • Routing
    • Observables (RxJS)
    • Forms
    • Pipes
    • HTTP
    • Modules
    • NgRx
    • Angular Universal
    • Tips
    • Standalone Components
  • An icon of the JavaScript section JavaScript
    • OOP
    • JavaScript - The Weird Parts
    • JS Functions
    • ES Modules
    • Node.js
    • Axios Tips
    • TypeScript
      • TypeScript Environment Setup
      • TypeScript Tips
    • React
      • React Routing
      • MobX
    • Advanced Vue.js Features
  • An icon of the Rust section Rust
    • Overview
    • Cargo
    • Basics
    • Ownership
    • Structures
    • Enums
    • Organization
    • Collections
    • Error Handling
    • Generics
    • Traits
    • Lifetimes
    • Closures
    • Raw Pointers
    • Smart Pointers
    • Concurrency
    • Testing
    • Tips
  • An icon of the C/C++ section C/C++
    • Compilation
    • Structures
    • OOP in C
    • Pointers
    • Strings
    • Dynamic Memory
    • argc and argv Visualization
  • An icon of the GTK section GTK
    • Overview
    • GObject
    • GJS
  • An icon of the CSS section CSS
    • Responsive Design
    • CSS Tips
    • CSS Pixel
  • An icon of the Unity section Unity
    • Unity
  • An icon of the Functional Programming section Functional Programming
    • Fundamentals of Functional Programming
    • .NET Functional Features
    • Signatures
    • Function Composition
    • Error Handling
    • Partial Application
    • Modularity
    • Category Theory
      • Overview
      • Monoid
      • Other Magmas
      • Functors
  • An icon of the Algorithms section Algorithms
    • Big O Notation
    • Array
    • Linked List
    • Queue
    • Hash Table and Set
    • Tree
    • Sorting
    • Searching
  • An icon of the Architecture section Architecture
    • What is architecture?
    • Domain-Driven Design
    • ASP.NET Core Projects

Basics

Bindings

Variables, or more properly, bindings look as follows:

let a = "abc";

An assignment like a = 10 produces a blank type () called unit. Whenever there is no other meaningful response type, () is returned.

Immutability

Bindings are immutable by default.

We can explicitly specify the type of a binding:

let a: i32 = 10;
let b = 10i32;
let c = 10_i32; // Underscore has no meaning, it can be used as a delimiter

Bindings may be shadowed, which means that a binding may be defined twice with the same name. This way, we don’t have to artificially define things like data_string and data. First, we could define data as String, and then define data as i32 with a value coming from parsed data.

Type Inference

Rust compiler is pretty smart about type inference. For example, this works:

let v: Vec<i32> = Vec::new();

The new() method of Vec is called without any generic type. Compiler sees that on the left side of the assignment, we specified the type to be i32.

This also works:

let mut vec = Vec::new();
vec.push(2);

The compiler “scans” the code and it can “see” that the vector instance should be created for the i32 type, because that’s what we’re adding to it later on.

Scope

Variables that go out of scope are automatically removed by Rust. We don’t need to free memory explicitly. There is no runtime overhead for that.

Mutable Reference vs. Mutable Value

let &mut x means that x points to some mutable value. However, x itself is not mutable, we can’t change what it points to.

let mut y = 4;
let x = &mut y;
*x += 1; // OK, value referenced by x is mutable
let mut z = 8;
x = &mut z; // NOT OK, x is immutable

Deep Copying

Heap data may be copied by value using the clone() method.

let s1 = String::from("hello");
let s2 = s1.clone();

s1 and s2 are two different variables pointing at different data in memory (but the values happen to be the same). clone() is much more expensive than a simple pointer copy.

Values that are stored on the stack (ints, floats, tuples (when they hold scalar values), arrays, chars, etc.) do not need that.

Functions

fn add(a: i32, b: i32) -> i32 {
i + j
}

The i + j has no semicolon. With a semicolon, the return type would be () instead of i32.

Functions return the last expression’s result by default, so return is not required.

Macros

Macro example:

println!("Hello");

Macros are similar to functions, but instead of returning data, they return code. They are often used to simplify common patterns.

In the case of printing, there are many ways of doing that depending on the provided data type. The println! macro takes care of figuring out the exact method to call.

Types

There is a large choice of number types. Conversions between types are always explicit. We use as for that.

Rust does not have constructors. Every type has a literal form (e.g. let a = Complex { re: 2.1, im: -1.4 };). Many types also have a new method (it’s not a Rust keyword though, these are just normal methods).

Loops

for item in collection {
}

After such an iteration, accessing collection is invalid! Rust assumes the collection is no longer needed, its lifetime is finished.

To circumvent it, include the & symbol to use a reference:

for item in &collection {
}

To be able to modify item during the iteration, a mutable reference should be used:

for item in &mut collection {
}

All three loops shown above are syntactic sugar for different method calls:

  • for item in collection = for item in itoIterator::into_iter(collection)
  • for item in &collection = for item in collection.iter()
  • for item in &mut collection = for item in collecion.iter_mut()

Index variable

for i in 0..collection.len() {
let item = collection[i];
}

Such a pattern is discouraged in Rust.

Anonymous loops

for _ in 0..10 {
}

Since a local variable is not needed, _ is used.

while(true) alternative

Rust has loop loop type:

loop {
//break eventually if needed
break;
}

It acts as while true, but is preferred.

Breaking out of nested loops

Loops can be labeled, and nested loops can be exited using the outer loop label.

'outer: for x in 0.. {
for y in 0.. {
for z in 0.. {
if x + y + z > 1000 {
break 'outer;
}
}
}
}

Conditions

There is no concept of truthy/falsey values. Conditions rely on true and false - that’s it.

match

There is match, which is similar to switch in other languages, but is much more powerful, because it supports expressions. Compilation fails if match does not cover all possible arms.

match item {
0 => {},
10..=20 => {},
40 | 80 => {},
_ => {}, // matches all others
}

match does not fall through through other cases. As soon as a case is matched, it’s executed, and match is exited.

match is very useful with enums.

match patterns can also provide values of internals of matched values:

match <[u8; 4]>::try_from(s.as_bytes()) {
Err(_) => Err(Box::new(InvalidStringError{})),
Ok(bytes @ [_, _, bit, _]) => if bit.is_ascii_alphabetic() {
Ok(Self {bytes})
} else {
Err(Box::new(InvalidStringError{}))
}
}

Above, both bytes (the whole array) and individual bit are accessible to the second match arm.

When dealing with match patterns, the values within them are consumed by default. Example:

let optional: Option<Point> = Some(Point { x: 2, y: 3 });
match optional {
Some(p) => println!("Point is {}", p),
_ => panic!("No match"),
}
println!("Optional is {}", optional);

The code above does not compile, since the value within the Option gets consumed on the highlighted line. The solution is to use ref:

match optional {
Some(ref p) => println!("Point is {}", p),
_ => panic!("No match"),
}

This way, instead of consuming Point, it’s borrowed (&Point).

Dropping temporary values

Some statements with let will not behave the same as statements without let.

Here’s an example with Mutex:

// OPTION I
loop {
let job = receiver.lock().unwrap().recv().unwrap();
//some long operation
}
// OPTION II
while let Ok(job) = receiver.lock().unwrap().recv() {
// some long operation
}

Mutex gets released when the result of lock() (an instance of MutexGuard) goes out of scope. In OPTION I, the lock will be removed right after the line where it’s acquired. The temporary value of the lock is not stored anywhere, and it is dropped right after the line. Another thread may acquire the lock while the current thread executes some other operations in that loop block.

In OPTION II, even though the lock result is not stored anywhere, it is still kept until the end of the while let block.

That behavior can be observed with while let, if let, and match.

Expressions

Almost everything is an expression. The following are possible:

let n = 123;
let desc = if is_even(n) {
"even"
} else {
"odd"
};
let desc2 = match is_even(n) {
true => "even",
false => "odd",
};

Even break returns a value:

let n = loop {
break 123;
}

The following are not expressions and, thus do not return values:

  • expressions delimited by ;
  • binding a name to a value with = (isn’t that already covered by the first rule anyway?)
  • type declarations (fn, struct, enum keywords)

References

References are created with &. They are dereferenced with *. In many cases, Rust will deference a reference automatically for us, for convenience.

Strings

There is a String type, which is stored on a heap. There is also a string literal, which is actually a string slice.

If a function expects a string parameter, it’s a good practice to use a string slice (&str) instead of a String, because:

  • a string literal can be passed as is;
  • a string slice can be easily taken out of a String with &some_string

A format! macro is useful for creating strings composed of other values:

let greeting = format!("My name is {} {}", first_name, last_name);

str is a dynamically sized type (DST) in Rust. It means that the contents of that type are not static and during runtime, the str value may be of various lengths (depending on the length of the string). Rust prefers types that have known sizes. That’s why we use &str instead, which is a slice - its size is known - it’s a pointer to str and a length of the value there (stored as usize).

OOP

Rust can be considered object-oriented or not, depending on the definition that we use. Some facts:

  • Rust has structs and enums that can include some data and methods - like objects in OOP
  • Parts of a struct may be public or private - encapsulation
  • There is no inheritance, but traits may have default implementations of methods
  • Some form of polymorphism can be achieved with generics
←  Cargo
Ownership  →
© 2023 Marcin Jahn | Dev Notebook | All Rights Reserved. | Built with Astro.