Skip to content

Commit a28331c

Browse files
committed
Add notes for Rust, C & C++, Graph Theory, DSP
1 parent 51ae8dd commit a28331c

File tree

89 files changed

+3139
-9
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

89 files changed

+3139
-9
lines changed

Advanced Features.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Advanced Features
2+
#rust
3+
4+
## [[Unsafe Rust]]
5+
6+
## [[Advanced Traits]]
7+
8+
## Advanced Types
9+
### Using the Newtype Pattern for Type Safety and Abstraction
10+
- The [[Advanced Traits#Implementing External Traits on External Types|newtype]] pattern is a lightweight way to achieve [[OOP Features of Rust#Characteristics of Object-Oriented Languages#Encapsulation|encapsulation]].
11+
12+
### Creating Type Synonyms with Type Aliases
13+
- Rust provides the ability to declare a _type alias_ to give an existing type another name. For this we use the `type` keyword.
14+
```rust
15+
type Kilometers = i32;
16+
let x: i32 = 5;
17+
let y: Kilometers = 5;
18+
println!("{}", x + y);
19+
```
20+
- This method merely renames the type and provides no type checking benefits that we got from the [[Advanced Traits#Implementing External Traits on External Types|newtype]] pattern.
21+
- The main sue case is to reduce repetition. For this reason they are also commonly used with the `Result<T, E>` type. For this reason, `std::io` has a type alias declaration:
22+
```rust
23+
type Result<T> = std::result::Result<T, std::io::Error>;
24+
```
25+
26+
### The Never Type that Never Returns
27+
- Rust has a special type named `!` that’s known in type theory lingo as the _empty type_ because it has no values.
28+
- Rust prefers to call it the _never type_ because it stands in the place of the return type when a function will never return.
29+
```rust
30+
fn bar() -> ! {
31+
// body
32+
}
33+
```
34+
- Functions that never return are called _diverging functions_. `bar` above is an example.
35+
- The never type is useful in situations where a function has to deal with `panic!` or `continue` statements. The Rust compiler does not permit a function to have different return types. For example, a function cannot return both an integer and a string based on a conditional statement. However, a situation like this is exactly what occurs in the following common-place code.
36+
```rust
37+
let guess: u32 = match guess.trim().parse() {
38+
Ok(num) => num,
39+
Err(_) => continue, // return type of continue is not same as num.
40+
}
41+
```
42+
- In the above example, `continue` expression has the type `!`. That is, when Rust computes the type of `guess`, it looks at both match arms, the former with a value of `u32` and the latter with a `!` value. Because `!` can never have a value, Rust decides that the type of `guess` is `u32`. This is also true for the `panic!` macro.
43+
44+
- Finally, a never ending `loop` also has the `!` type.
45+
46+
### Dynamically Sized Types and the `Sized` Trait
47+
- Dynamically Sized Types or DSTs or unsized types let us write code using values whose size we can know only at runtime.
48+
- An example of a DST in Rust is `str`. We can’t know how long the string is until runtime, meaning we can’t create a variable of type `str`, nor can we take an argument of type `str`.
49+
- **Rust needs to know how much memory to allocate for any value of a particular type, and all values of a type must use the same amount of memory**. This is why it is not possible to create a variable holding a dynamically sized type.
50+
- Therefore, Rust uses the `&str` type for string slices. A `&str` is _two_ values: the address of the `str` and its length. As such, we can know the size of a `&str` value at compile time: it’s twice the length of a `usize`
51+
- **The golden rule of dynamically sized types is that we must always put values of dynamically sized types behind a pointer of some kind.**
52+
53+
- Every trait is a dynamically sized type we can refer to by using the name of the trait.
54+
- To work with DSTs, Rust provides the `Sized` trait to determine whether or not a type’s size is known at compile time. This trait is automatically implemented for everything whose size is known at compile time.
55+
- In addition, Rust implicitly adds a bound on `Sized` to every generic function. That is, a generic function definition like this:
56+
```rust
57+
fn generic<T>(t: T) { }
58+
// is equivalent to
59+
fn generic<T: Sized>(t: T) { }
60+
```
61+
62+
- By default, generic functions will work only on types that have a known size at compile time. However, you can use the following special syntax to relax this restriction:
63+
```rust
64+
fn generic<T: ?Sized>(t: &T) { } // Only available for Sized trait.
65+
```
66+
## [[Advanced Function and Closures]]

Advanced Function and Closures.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
## Advanced Function and Closures
2+
### Function Pointers
3+
- Similar to how [[Closures|closures]] can be passed to functions, functions can also be passed to functions.
4+
- Functions coerce to the type `fn` (not the same as `Fn`). The `fn` type is called a *function pointer*.
5+
```rust
6+
fn add_one(x: i32) -> i32 {
7+
x + 1
8+
}
9+
10+
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
11+
f(arg) + f(arg)
12+
}
13+
14+
fn main() {
15+
let answer = do_twice(add_one, 5);
16+
}
17+
```
18+
- Unlike closures, `fn` is a type rather than a trait. Therefore, `fn` is used directly as the parameter type rather than declaring a generic type parameter with one of the `Fn` (closure) traits as the trait bound.
19+
- Function pointers implement all three of the closure traits (`Fn`, `FnMut`, and `FnOnce`), which means `fn` can always be passed as an argument for a function that expects a closure. An example of such a function is:
20+
```rust
21+
let list = vec![1, 2, 3];
22+
23+
// covert elements to string using closure
24+
let list_strings = list.iter().map(|i| i.to_string()).collect();
25+
26+
// using function pointers instead
27+
let list_strings = list.iter().map(ToString::to_string).collect();
28+
```
29+
- This can be a useful mechanism for generating enum variants. Recall that [[Defining an Enum|enum variants are themselves initializer functions]].
30+
```rust
31+
enum Status {
32+
Value(u32),
33+
Stop,
34+
}
35+
let list_statuses = (0u32..20).map(Status::Value).collect();
36+
```
37+
38+
### Returning Closures
39+
- Since closures are represented by traits, they cannot be returned directly.
40+
- We have already seen one technique for [[Closures|returning closures]]. This is recalled in the code snippet below. Depending on the functionality implemented by the closure the trait can be `Fn`, `FnMut`, or `FnOnce`.
41+
```rust
42+
fn return_closure() -> impl Fn(i32) -> i32 { // can be FnMut or FnOnce
43+
|x| x + 1
44+
}
45+
```
46+
- In situations where Rust is unable to understand how much size is required to store the closure, a [[Smart Pointers#Box Pointer Using `Box<T>` to Point to Data on the Heap|box pointer]] with a `dyn` keyword (similar to [[Trait Objects]]) is necessary.
47+
```rust
48+
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
49+
Box::new(|x| x + 1)
50+
}
51+
```

Advanced Traits.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
## Advanced Traits
2+
### Specifying Placeholder Types in Trait Definitions with Associated Types
3+
- _Associated types_ connect a type placeholder with a trait such that the trait method definitions can use these placeholder types in their signatures.
4+
- The implementor of a trait will specify the concrete type to be used instead of the placeholder type for the particular implementation.
5+
- That way, we can define a trait that uses some types without needing to know exactly what those types are until the trait is implemented.
6+
```rust
7+
pub trait Iterator {
8+
type Item;
9+
10+
fn next(&mut Self) -> Option<Self::Item>;
11+
}
12+
```
13+
- Although associated types look similar to generic types, they have some differences: With generics, types need to be annotated with each implementation. This implies that the same trait can be implemented multiple times, with the generic type being replaced with a concrete type each time. However, with associated types, we can only have one concrete type replace the associated type since there can only be one `impl` block with a unique method name.
14+
- For the above reason, associated type also becomes a part of the trait's contract.
15+
16+
### Default Generic Type Parameters and Operator Overloading
17+
- When we use generic type parameters, we can specify a default concrete type for the generic type.
18+
- This is done when declaring a generic type with the `<PlaceholderType=ConcreteType` syntax.
19+
- This technique is useful in operator overloading, which is used to customize the behavior of existing operators such as `+`. Operator overloading is done by implementing the traits associated with the operator. For example, the `Add` trait is defined as in the following example.
20+
```rust
21+
trait Add<Rhs=Self> {
22+
type Output;
23+
fn add(self, rhs: Rhs) -> Self::Output;
24+
}
25+
```
26+
- Since add provides a default type, it can be overloaded. The following example shows operator overloading for adding millimeters and meters together.
27+
```rust
28+
use std::ops::Add;
29+
30+
struct Millimeters(u32);
31+
struct Meters(u32);
32+
33+
impl Add<Meters> for Millimeters {
34+
type Output = Millimeters;
35+
fn add(self: other: Meters) -> Millimeters {
36+
Millimeters(self.0 + (other.0 * 1000))
37+
}
38+
}
39+
```
40+
- Default type parameters are used in two ways:
41+
- To extend a type without breaking existing code.
42+
- To allow customization in specific cases most users won't need.
43+
44+
### Fully Qualified Syntax for Disambiguation
45+
- Sometimes, certain traits implemented on a data type may have the same names for the associated functions. In these situations disambiguation is necessary to ensure that the Rust compiler understands exactly which associated function is being called.
46+
- The disambiguation needs to deal with two scenarios: when the associated function is a method, i.e., when it takes a self parameters, and when it is not a method.
47+
- Consider the following example with different trait methods having the same name.
48+
```rust
49+
trait Pilot {
50+
fn fly(&self);
51+
}
52+
53+
struct Human;
54+
55+
impl Pilot for Human {
56+
fn fly(&self) {
57+
println!("This is your captain speaking");
58+
}
59+
}
60+
61+
impl Human {
62+
fn fly(&self) {
63+
println!("*waving arms furiously*");
64+
}
65+
}
66+
67+
fn main() {
68+
let person = Human;
69+
Pilot::fly(&person); // disambiguate fly for Pilot trait
70+
person.fly(); // equivalent to Human::fly(&person)
71+
}
72+
```
73+
74+
- The format of disambiguation used above will not work when the associated function is not a method, i.e., when it does not take a self parameter. In this case, it is impossible for Rust to tell which function of the many different data structures implementing the trait is being called. A fully qualified syntax is needed for discerning.
75+
```rust
76+
trait Animal {
77+
fn baby_name() -> String;
78+
}
79+
80+
struct Dog;
81+
82+
impl Dog {
83+
fn baby_name() -> String {
84+
String::from("Spot")
85+
}
86+
}
87+
88+
impl Animal for Dog {
89+
fn baby_name() -> String {
90+
String::from("puppy")
91+
}
92+
}
93+
94+
fn main() {
95+
println!("{}", Dog::baby_name());
96+
println!("{}", <Dog as Animal>::baby_name()); // Animal::baby_name() will not work
97+
}
98+
```
99+
100+
### Using Supertraits
101+
- A supertrait is a trait that uses the functionality of another trait in its own definition.
102+
- The following is an example of a supertrait `OutlinePrint` that formats the output of any data type implementing the `Display` trait. The format for specifying that the trait depends on another trait is
103+
```rust
104+
use std::fmt;
105+
106+
trait OutlinePrint: fmt::Display { // syntax
107+
fn outline_print(&self) {
108+
// default implementation
109+
}
110+
}
111+
```
112+
113+
### Implementing External Traits on External Types
114+
- The orphan rule that states we’re only allowed to implement a trait on a type if either the trait or the type are local to our crate.
115+
- It’s possible to get around this restriction using the _newtype pattern_, which involves creating a new type in a tuple struct.
116+
- As an example, let’s say we want to implement `Display` on `Vec<T>`, which the orphan rule prevents us from doing directly because the `Display` trait and the `Vec<T>` type are defined outside our crate. We can make a `Wrapper` struct that holds an instance of `Vec<T>`; then we can implement `Display` on `Wrapper` and use the `Vec<T>` value.
117+
118+
```rust
119+
use std::fmt;
120+
121+
struct Wrapper(Vec<String>); // work-around
122+
123+
impl fmt::Display for Wrapper {
124+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
125+
write!(f, "[{}]", self.0.join(", "))
126+
}
127+
}
128+
129+
fn main() {
130+
let w = Wrapper(vec![String::from("hello"), String::from("world")]);
131+
println!("w = {}", w);
132+
}
133+
```
134+
- The downside is `Wrapper` is a new type, so it doesn’t have the methods of the value it’s holding.
135+
- If we wanted the new type to have every method the inner type has, implementing the [[Smart Pointers#Treating Smart Pointers Like Regular References with the `Deref` Trait|Deref]]` trait.

Bipartite Graphs.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
## Bipartite Graphs
2+
#graphTheory
3+
4+
- A graph $G$ is *Bipartite* if its vertices can be partitioned into two disjoint sets $L$ and $R$ such that:
5+
- Every edge of $G$ connects a vertex $L$ to a vertex in $R$.
6+
- No edge connects two vertices from the same set.
7+
- $L$ and $R$ are called the *parts* of $G$.
8+
![[Pasted image 20230724194906.png]]
9+
- **A graph is bipartite, $iff$ it has no cycles of odd length**.
10+
- A cycle graph with even vertices is a bipartite graph. Not true if number of vertices is odd.
11+
- Trees are bipartite as well.
12+
- A *Complete Bipartite Graph* $K_{L, R}$ is a bipartite graph in which all vertices of $L$ are connected to all vertices of $R$ and vice versa.
13+
14+
### Matchings
15+
- A matching in a graph is a set of edges without common vertices.
16+
- A *maximal matching* is a matching which cannot be extended to a larger matching.
17+
- A *maximum* matching is a matching of the largest size.
18+
19+
- In bipartite graphs, matchings can be used to find mapping solutions from one part to the other.
20+
21+
### Hall's Theorem
22+
- **In a bipartite graph $G = (L \cup R, E)$, there is a matching which covers all vertices from L, $iff$ for every subset of vertices $S \subseteq L$, $$|S| \leq |N(S)|$$
23+
- Here, the *Neighborhood* $N(S)$ of $S$, a subset of vertices $S \subseteq V$ of graph $G = (V, E)$, is defined as the set of all vertices connected to at least one vertex in $S$.

Box Pointer.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
## Using `Box<T>` to Point to Data on the Heap
2+
- Boxes allow storing data on the heap rather than the stack.
3+
- Boxes don't have performance overhead. They are most often used for:
4+
- Types whose size can't be know at compile time.
5+
- Ownership of large amounts of data that needs to be moved instead of copied.
6+
- Owning a value of a type that implements a particular trait.
7+
- Creating a new box
8+
```rust
9+
fn main() {
10+
let b = Box::new(5);
11+
println!("{}", b);
12+
}
13+
```
14+
15+
### Enabling Recursive Types with Boxes
16+
- Recursive types can have another value of the same type as a part of itself.
17+
- Since the Rust compiler has no way of understanding how much space a recursive type takes up, these types can become an issue.
18+
- Using boxes can help break the recursive type definition into
19+
- An example of a recursive type is a cons list, which is a data structure containing a nested pair. The first element in the pair is the value of the current item and the second element is the next value.
20+
`(1, (2, (3, Nil)))`
21+
- A basic implementation in Rust would look like the following code. However, the compiler will throw an error since it is unable to assess the amount of space required for this data type.
22+
```rust
23+
enum List {
24+
Cons(i32, List),
25+
Nil,
26+
}
27+
```
28+
![[Pasted image 20230615161555.png]]
29+
30+
- The workaround here would be store the value indirectly by storing a pointer to the value instead.
31+
```rust
32+
enum List {
33+
Cons(i32, Box<List>),
34+
Nil,
35+
}
36+
```
37+
![[Pasted image 20230615161921.png]]

0 commit comments

Comments
 (0)