-
Notifications
You must be signed in to change notification settings - Fork 0
/
notes.txt
620 lines (441 loc) · 16.7 KB
/
notes.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
Let’s recap what we’ve learned so far about Cargo:
We can create a project using `cargo new`.
We can build a project using `cargo build`.
We can build and run a project in one step using `cargo run`.
We can build a project without producing a binary to check for errors using `cargo check`.
Instead of saving the result of the build in the same directory as our code, Cargo stores it in the `target/debug/` directory.
Building for Release
When your project is finally ready for release, you can use `cargo build --release` to compile it with optimizations.
This command will create an executable in `target/release/` instead of `target/debug/`. The optimizations make
your Rust code run faster, but turning them on lengthens the time it takes for your program to compile. This is why
there are two different profiles: one for development, when you want to rebuild quickly and often, and another for building
the final program you’ll give to a user that won’t be rebuilt repeatedly and that will run as fast as possible.
If you’re benchmarking your code’s running time, be sure to run `cargo build --release` and benchmark with the executable
in `target/release/`.
[September 5, 2023]
to include bult in library we need use `use` keyword as `#include` in c programmin language.
[April 11, 2024]
Cargo initializes git on new projects. It does not, if new project is created already in git.
To avoid git initialization, we can use --vcs flag.
[April 12, 2024]
We can add external crate to Cargo.toml file to use in projetc.
as rand:
rand = "0.8.5"
We can update used crates with 'cargo update'
By default, variables are immutable in rust, to make them mutable, the 'mut' keyword need to be used
let mut num = 7;
Can't be used variables created with 'let' in blobal scope, but with 'conts' can be.
The constant variables are always immutable, the type of value must be annotated and can be assigned to only
constant expression, not to a value that can be calculates at runtime. And, uppercase is used for constants.
const NUM: i8 = 127;
We can shadow variables
let x = 5;
let x = x + 18;
last version of x will be used, this is shadowing.
mut vs shadowing
shadowed variable at the end stays immutable and its type can be changed while shadowing
with the same variable name.
let spaces = " ";
let spaces = spaces.len();
[April 14, 2024]
Rust is expression based language. Need to understand the difference between
statement and expression in rust.
A statement does an action but does not return value.
An expression finalize a rezult and returns it.
A funtion call is expression. Macros are expression. But function signeture is statement.
Variables are statement.
let a = (let x = 7); // gives error
let y = {
let x = 4;
x+2
}
// here y is equials 6, how?
Because the last statement in expression is returned.
As we can see above, there is no semicolon after last state-
ment. If we put semicolon after last statement, whole expression
becomes statment and it does not work as expected.
fn five() -> u8 {
5
}
here is the function five is valid function and it returns five with
return type of u8 (unsigned 8 byte integer).
We can set parametres with its type in functions.
fn muliply_by2(num: i128) -> i128 {
num*2
}
Control flow
In rust control flow is expresion, as we can see here
let condition = true;
let number = if condition {5} else {7};
println!("number is {number}");
-----
let num = if condition { "5" } else { 6 };
Here this code gives error, because both if and else arms should return the same type
of data and the first value's type is expected for following conditions.
"5" is string, so the the 6 is also expected to be string, so error will be thrown here.
Loops
In rust there are three type of loops, loop, while and for;
loop iterates forever,
loop {
println!("again!");
}
we can stop it with break keyword or skip some iterations with continue keyword
loop {
if condition {
break;
}
if condition2 {
continue;
}
}
loop is also expression, so we can return a vlaue
loop {
if condition {
break value;
}
}
---
let mut number = 0;
let end_loop_result = loop {
number += 1;
if number == 10 {
break number;
}
};
println!("end loop result {end_loop_result}");
---
we can also label a loop and break it with its label. To create lable we use single
quote then label and colon as following:
'label: loop {
}
Here an factory example, which produces material untill it has gas.
When gas finishes the engine turns off. When the target amount of material is made
the whole factory gets turns off and prints produced material emount.
------
let mut material = 0;
let target = 10;
'factory: loop {
let mut gas = 3;
println!("Material amount: {material}");
println!("Starting engine!");
loop {
println!("Gas left: {gas}");
if gas == 0 {
println!("Stopping engine!");
break;
}
if material == target {
println!("We got enough, stopping factory!");
break 'factory;
}
gas -= 1;
material += 1;
}
}
println!("Material are made: {material}");
------
[April 15, 2024]
Ownership
The Ownership is a set of rules rust uses for memory managment.
There are three simple rules:
i) any value has an owner
ii) a vlue can have only one owner at a time
iii) when the scope ends a value in, the value is dropped
To understand the ownership, we need to understand the stack and heap.
In rust some data are stored in stack and some in heap based on their types.
We can see with the example of string.
When we create string literal:
let s = "hello";
this is hard coded and not expected to change at runtime or compile time.
if it is supposed to change, needed to use another type of string String:
let mut s = String::from("Hello");
s.push_str(", World");
This type of string is stored on heap and can be modified. Its data is stored at heap,
but the pointer adress to this string is stored in stack.
let s1 = String::from("Hello");
let s2 = s1;
println!("{}", s1);
above the code gives error. Because s1 and s2 are could point to one pointer and when the scope ends,
they try to free the memory on heap twice, which coauses memory curruption. To prevent this, in rust,
s1 is moved to s2, so after s2 the s1 is invalidated. This is colled Move in rust.
But some times we need to refence duplicates, so in that case we can clone it.
let s1 = String::from("Hello");
let s2 = s1.clone();
println!("{}", s1);
A function can return two values with tuple
Reference
we can refence a value without passing it to function with reference.
fn main() {
let g = String::from("Hello, there");
let l = get_len(&g);
println!("The length of '{}' is {}", g, l);
}
fn get_len(s: &String) -> usize{
s.len()
}
Referenced value are immutable as variable by default. But, we can make it mutable
with "mut":
fn main() {
let mut s = String::from("Hello, there | ");
changes(&mut s);
println!("{}", s);
}
fn changes(s: &mut String) {
s.push_str("Hello there");
}
One restriction with referenced mutable value is it can only has one reference at a time. Following
code fails:
let mut s = String::from("Brendan");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2);
the same rule applies for mutable and imutable references at a time:
let mut s = String::from("Brendan");
let r1 = &s;
let r2 = &mut s;
the immutable references are need to be used before initialization of mutable refence.
Otherwise it causes error:
this code fails:
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
let r3 = &mut s;
println!("{}", r2); // here r2, immutable ref is being used after muatble
println!("{}", r3);
this code works:
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
let r3 = &mut s;
println!("{}", r3);
[April 23, 2024]
References and Borrowing
In rust, we can reference to a value and mutate it, if it is mutable or we can just use it's value. With reference
we do not have to pass an ownership and return it back again. We can simple use any value's by it is reference
as following:
fn main() {
let str = String::from("Brendan Eich");
println!("The length of '{}' is {}", str, get_len(&str));
}
fn get_len(str: &String) -> usize {
str.len()
}
We call the action of creating a reference borrowing. As in real life, if a person owns something,
you can borrow it from them. When you’re done, you have to give it back. You don’t own it.
References are also can be mutable and immutable as variables.
If we look at the code above, the str variable is immutable, so if we try to modify it in get_len() function
we get an error.
fn get_len(str: &String) -> usize {
str.push_str("change!");
str.len()
}
So make it mutable we need to make the variable mutabel and pass it to function as mutable as following:
---
let mut str = String::from("Brendan Eich");
---
get_len(&mut str);
---
fn get_len(str: &mut String) -> usize {}
---
as we can see we need to show the mutabilit of variable anywhere.
The Rules of References
Let’s recap what we’ve discussed about references:
At any given time, you can have either one mutable reference or any number of immutable references.
References must always be valid.
[April 25, 2024]
The Slice Type
we can reference to some parts of String as following:
let s = String::from("hello");
let len = s.len();
let slice = &s[3..len];
let slice = &s[3..];
[..] - referencing to whole word
[3..] - referencing from 3rd till the end of a word
[..3] - referencing till the 3rd character of the word
[inclusive, explusive]
[April 26, 2024]
Using Structs
We can create structs as following:
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
and we can set it values as following:
let user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
If we want to update any property of a struct we need to make it mutable.
We can not make mutable a specific property of a struct. We need to make whole
struct mutable, then we can update any property of a struct as following:
let mut user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
user1.email = String::from("anotheremail@example.com");
"field init shorthand syntax"
this syntax allows us to set properties of struct in an easy way.
In the following function, instead of "username: username", we just set to "username".
Because the property of key word and function parametres name are the same:
---
println!(
"{:#?}",
build_user(String::from("jony"), String::from("jony@nasa.moon"))
);
---
fn build_user(username: String, email: String) -> User {
User {
active: true,
username, // we are not doing "username: username" because they are the same.
email, // the same here
sign_in_count: 1,
}
}
We can update a struct instance with another one as following:
---
let user2 = User {
email: String::from("new_user@nasa.mars"),
..user1 // copying all properties of user1, exept "email"
};
---
[April 28, 2024]
We should keep in mind after, above update that, the username and email hold Sring as data, so String does not
have default Copy trait as premitive types, like sign_in_count and active. Those two, active and sign_in_count
properties have Copy trait, so they are copied automatically to user2. Which mens they are still available in
user1.
So, the user2 have email already, the username is moved to user2 form user1, so username will not be available
in user1 after update.
In short, types which have Copy trait are copied and available in the source struck. The types which do not have
Copy trait are moved to new struck, then they will not be available in the source struck. Updating struck with other
struck's instnce is equals to "=" as assignment.
Using Tuple Strucks
We use tuple structs when naming the fields are verbose and redundant.
The tuple structs are similar to simple struckts:
struct Point(i32, i32, i32);
struct Color(u8, u8, u8);
Debugging structs
To debug we use logging while programming. In rust we can not log a struct directly.
We need to tell exactly a stuct can be debugged as folling:
#[derive(Debug)] // this is what we need
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
then we can log the struct in console. To log struct we use {:?} to tell log in a debugging mode.
We can also format out output by adding grid sign, {:#?}
println!("The user is {:#?}", user1);
Another way to debug is dbg! macro. It prints file and the line number.
dbg!(&user1);
---
[area.rs:12:5] &rect1 = User { // the file and line number
...
}
we can use dbg! trait for explressions as following:
let rect1 = Rectangle {
w: dbg!(15 * 2), // here
h: 50,
};
---
output:
[area.rs:9:12] 15 * 2 = 30 // file and line number
Methods in structs
We can use methods in struct, and do calculations with that struct's properties.
To implement a method to a struct:
struct Rectangle {
w: i32,
h: i32,
}
impl Rectangle { // implementing struct method
fn area(&self) -> i32 {
self.w * self.h
}
}
Methods get the first paramets the self, the instance of struct always.
Here, the area is using reference type of struct. Which only reads poperties of structs.
We can also update the instance in method:
---
fn area(&mut self) -> i32 { // mutable
}
---
We can create a struct property with the same name with method.
Here we can know the width is higher that zero or not.
struct Rectangle {
w: i32, // w name
h: i32,
}
impl Rectangle {
fn w(&self) -> bool { // w name
self.w > 0
}
}
In structs we can define associated function. The difference between associated functions and Methods
is the associated functions' first parametr is not the &self.
impl Rectangle {
fn square(side: i32) -> Self {
Self {
w: side,
h: side,
}
}
}
To access associated functions, we use "::", as String::new() or String::from():
let sqr1 = Rectangle::square(30); // here "::"
println!("Square: {:#?}", sqr1); // printing in debug mode
[May 6, 2024]
Enums and Pattern matching
Enums are similar to structs. Enums are supposed to store different version
of one type.
For example we can use for IP versions as following:
enum IpAddrKind {
V4,
V6,
}
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
then we can pass to a function:
fn route(ip_kind: IpAddrKind) {}
We can combine structs and enums each other as properties:
enum IpAddrKind {
V4,
V6,
}
struct IpAddr {
kind: IpAddrKind,
address: String,
}
We can make this code more cleaner as following:
enum IpAddr {
V4(String),
V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
as you can see, enum itself can handle all of these.
Another, plus side of enums is each enum property can handle different
type of data types based on the data it stores, as following:
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
We can define methods in enums as structs as following:
enum Message {
Write(String),
}
impl Message {
fn call(&self) {
// method body would be defined here
}
}
let m = Message::Write(String::from("hello"));
m.call();
Option Enum