Alex Takes Notes

Hi! I’m Alex and I take notes on everything I do. So I made this site a place to store them.

Update log

16.03.2023 Article on Rust Traits

06.03.2023 Article on Rust Generics

28.02.2023 Added info to Rust strings

17.02.2023 Article on Handling errors in Rust

15.02.2023 Article on Rust Hashmap

13.02.2023 09.02.2023 08.02.2023 07.02.2023

01.02.2023 Article on Rust structs

30.01.2023 29.01.2023

20.01.2023 Article on Rust Variables

13.01.2023 12.01.2023

11.01.2023 I just started out.

Subsections of Alex Takes Notes

Chapter 1

Development

Find out how to create and organize your content quickly and intuitively.

  • Git

    Using Git repository

  • Rust

    Rust language notes

Subsections of Development

Git

Configuring Git

git config --global user.name "spider_net" # username init, seen in commits

git config --global user.email "test@gmail.com" # Init of user e-mail

Cloning Repository

git clone https://github.com/drupal/drupal.git drupal7

Initializing Local & Remote Repo

git init # local repo init

git status # check status

git branch -m master main # change master branch to main, for GitLab

git add * # add all files to repo, .gitignore file lets you hide junk files, password files from syncing

git commit -m "Hello world"

git config --global user.email "Ivan@example.com"
git config --global user.name "Ivan the Terrible"

git log

git remote add origin git@gitlab.com:<name of account>/<project name>.git

git push origin main

Branching

Create new branch: git branch new-branch Pointer does not automatically switch to new branch, it is left on main branch, so you need to switch manually: git checkout new-branch

Merging Branches

  • Switch to branch for merging
  • Merge
  • Check pointers point to one place OR merge the other branch with the current one
git commit
git checkout main
git commit
git merge new-branch

Change of branches to make it look as if the functionality was developed step-by-step, not in parallel.

git rebase master
git checkout master
git rebase bugFix

Pointer

HEAD is the pointer to current project state. By default HEAD points to current branch. You can go back by commits or by direct commit hash: git checkout C1

You can use syntax “^K” for going up 1 level (where K is the route number to choose if there are more than 1 parent. By default, K=1) and “~N” for N steps up:

git checkout master^
# OR
git checkout bugFix^^
# OR
git checkout bugFix~2
# same route on schema from picture
# or build a sequence
git checkout bugFix^~1

Chaning main branch to several commits backwards:

git branch -f master HEAD~3
# -f means force - direct remap of branch for commit

Undo Actions

For 1-person local repo do reset. For multi-person remote repo, do revert.

git reset HEAD~1
# OR
git revert HEAD

reset revert In c2’ state are the changes, which cancel state C2

Moving commits

Chosen commits are moved upon current commit using cherry-pick command. Used when it is known what specific commits to move: git cherry-pick C2 C4 git rebase -i HEAD~4 --aboveAll

Small commit modifications

You can modify a commit, dividing it into two with same parent: git commit --amend

Tagging commits

Marking milestones is done with tags. They block the commit from changes, moving etc.:

git tag v1 <hash> # if hash is not provided, then current commit is tagged

Tags serve as anchors in tree of commits. To define your position against the nearest anchor, use command:

git describe <ref> # if <ref> is not provided, current commit will be described

Naming

Remote branches have a naming convention: <remote name>/<branch name>

Main remote i called origin. So master branch is origin/main. When doing commit in local branch, the system is put into detached HEAD mode:

git checkout origin/master
git commit

Fetch data from remote repository

When data is downloaded from remote repo, the origin/main branch is updated to reflect the new commits:

git fetch

Only commits non-present locally are downloaded. No local state of files is changed after download. The local main status is unchanged. To change, a merge must be done:

git fetch + git merge = git pull

git pull

Publishing local repository to remote

Publishing syncs commits at remote repo and local repo (main and origin/main point to the same commit):

git push

If the remote repo has changed by someone by the time you need to push there, it means that your feature is based on an old commit. So Git will not let you push. Before push, a fetch must be made to sync the changes, than a rebase or merge to update the main branch, and then a push:

git fetch + git rebase origin/main + git push OR git fetch + git merge origin/main + git push = git pull –rebase

git pull --rebase

Changing main and origin/main

You can change that another branch will be main for the remote repo:

`git checkout -b side origin/main

Push arguments

You can specify what branch to push:

git push origin main

origin = remote repo, main = branch to take commit from.

It does not matter where the HEAD is at this moment. You can specify where to push commits using git push origin <source>:<destination> notation:

git push origin side^2:master

If push is made to a non-existent branch on remote repo, Git will create this branch:

git push origin main:newBranch

Fetch arguments

Same as push, but the other wat around. Go to foo branch on remote repo, download commits from there to local branch origin/foo:

git fetch origin foo

Rust

Articles in Section

Rust Tools

External link - https://www.youtube.com/watch?v=ifaLk5v3W90

git clone https://github.com/AstroNvim/AstroNvim ~/.config/nvim
nvim +PackerSync

# After install use commands:
:LspInstall rust -> rust-analyzer
:TSInstall rust
  • Neovide GUI upgrade on Astro Vim
git clone https://github.com/neovide/neovide
cd neovide
cargo build --release
  • EVCXR or iRUST REPL
cargo install evcxr_repl
cargo install irust
cargo install --locked bacon
bacon test # run from project folder 

Cargo

Use Cargo for managing projects.

cargo new test_project // create project with binary file src/main.rs
// OR
cargo new test_project --lib // create project with library file src/lib.rs 

cd test_project

Source code is in src folder.

cargo build # build project for debug, do not run
cargo run # build & run project
cargo check # fast compiling
cargo build --release # slow build with optimizations for speed of release version

Documentation of methods & traits used in code can be compiled an opened in browser with Cargo command:

cargo doc --open

Panic Response

В ответ на панику, по умолчанию программа разматывает стек (unwinding) - проходит по стеку и вычищает данные всех функций. Для уменьшения размера можно просто отключать программу без очистки - abort. Для этого в файле Cargo.toml надо добавить в разделы [profile]:

[profile.release]
panic = 'abort'

Cargo Clippy linter

Example of Clippy config:

cargo clippy --fix -- \
-W clippy::pedantic \
-W clippy::nursery \
-W clippy::unwrap_used \
-W clippy::expect_used

Clippy has a markdown book:

cargo install mdbook
# Run from top level of your rust-clippy directory:
mdbook serve book --open
# Goto http://localhost:3000 to see the book

Important Cargo libs

  • Tokio - async runtime
  • Eyre & color-eyre - type signatures, error report handling
  • Tracing - collect diagnostic info
  • Reqwest - HTTP requests library, async
  • Rayon - data parallelism library, without data races
  • Clap - commandline passing library
  • SQLX - compile-checked SQL, async
  • Chrono - time/date library
  • EGUI - web-gui @60FPS, runs in Webassembly
  • Yew.rs - web-library like React.js

Rust Prelude

Rust has a Prelude - a set of libraries included in every project. See current libs included in Prelude - https://doc.rust-lang.org/stable/std/prelude/index.html

User input

std::io::stdin library is used to get user input from standard input stream. Not included in Prelude:

use std:io

let mut guess = String::new();

io::stdin().read_line(&mut guess).expect("Failed to load");

.expect handles Err variant of read_line function, crashing the program with the defined error message.

Subsections of Rust

CLI Arguments

ARGS

Аргументы командной строки можно захватить с помощью методов args()+collect() библиотеки env. Нужно поместить их в строковый вектор.

use std::env;  
  
fn main() {  
    let args: Vec<String> = env::args().collect();  
  
    for arg in args.iter() {  
        println!("{}", arg);  
    } }
Tip

Продвинутую обработку аргументов CLI удобно делать с помощью библиотеки Clap https://docs.rs/clap/latest/clap/

Constants

Константы

Это глобальные по области действия значения, неизменяемые. При объявлении нужно всегда сразу указывать их тип.

const MAX_LIMIT: u8 = 15;  // тип u8 обязательно надо указать
  
fn main() {  
    for i in 1..MAX_LIMIT {  
        println!("{}", i);  
    }  }

Enums

Перечисления

Тип перечислений позволяет организовать выбор из нескольких вариантов в пределах логического множества.

enum Money {  
    Rub,  
    Kop  
}

match

Аналог switch в других языках, однако, круче: его проверка не сводится к bool, а также реакция на каждое действие может быть блоком:

fn main() {  
    let m = Money::Kop;  
    println!("Я нашёл кошелёк, а там {}p",match_value_in_kop(m));  
}  
  
fn match_value_in_kop(money: Money) -> u8 {  
    match money {  
        Money::Rub => 100,  
        Money::Kop => {  
            println!("Счастливая копейка!");  
            1  
        }  }  }

if let

В случае, когда выбор сводится к тому, что мы сравниваем 1 вариант с заданным паттерном и далее запускаем код при успехе, а в случае неравенства ничего не делаем, можно вместо match применять более короткую конструкцию if-let:

    let config_max = Some(3u8);
    match config_max {
        Some(max) => println!("The maximum is configured to be {}", max),
        _ => (), //  другие варианты ничего не возвращают
    }

Превращается в:

    let config_max = Some(3u8);
    if let Some(max) = config_max {
        println!("The maximum is configured to be {}", max);
    }

Применение if-let - это синтаксический сахар, укорачивает код, однако, лишает нас проверки наличия обработчиков на все варианты возвращаемых значений как в конструкции match.

Error Handling

External link: https://youtu.be/f82wn-1DPas

Подготовка примера

Допустим, мы берём вектор из строк-чисел, складываем их и возвращаем сумму как строку:

fn sum_str_vec (strs: Vec<String>) -> String {  
    let mut accum = 0i32;  
    for s in strs {  
        accum += to_int(&s);  
    }  
    return accum.to_string();  
}  
  
fn main() {  
    let v = vec![String::from("3"), String::from("4")]; // Правильный ввод 
    let total = sum_str_vec(v);  
    println!("Total equals: {:?}", total);  
  
    let v = vec![String::from("3"), String::from("abc")]; // Неправильный ввод 
    let total = sum_str_vec(v);  
    println!("Total equals: {:?}", total);  
}

Для конвертации строки в числа, нужно реализовать функцию to_int в соответствии со стратегиями обработки ошибочного ввода. Конвертацию мы делаем функцией parse(), которая возвращает тип Result<T,E>, где T - значение, E - код ошибки.

Стратегия 1 - паника

В случае неверного ввода, программа полностью останавливается в панике. Метод unwrap() у типа Result<T,E> убирает проверки на ошибки и есть договор с компилятором о том, что ошибки в этом месте быть не может. Если она есть, программа падает с паникой:

fn to_int(s: &str) -> i32 {  
    s.parse().unwrap() } 

Стратегия 2 - паника с указанием причины

В случае неверного ввода, программа сообщает фразу, заданную автором, далее полностью останавливается в панике. Метод expect() аналогичен unwrap(), но выводит сообщение:

fn to_int(s: &str) -> i32 {  
    s.parse().expect("Error converting from string") } 

Стратегия 3 - обработать то, что возможно обработать

Можно сконвертировать и прибавить к результату те строки, которые позволяют это сделать, проигнорировать остальные. Метод unwrap_or() позволяет указать возвращаемое значение в случае ошибки:

fn to_int(s: &str) -> i32 {  
    s.parse().unwrap_or(0) } // при вводе "abc" вернётся 0, сумма = "3" 

Более предпочтительный вариант использовать закрытие unwrap_or_else(), так как метод unwrap_or() будет вызван ДО того как будет отработана основная команда, ВНЕ ЗАВИСИМОСТИ от того, будет ли её результат Some или None. Это потеря производительности, а также потенциальные глюки при использовании внутри unwrap_or() сложных выражений. Закрытие unwrap_or_else() будет вызвано только в случае None, иначе же эта ветка не обрабатывается:

fn to_int(s: &str) -> i32 {  
    s.parse().unwrap_or_else(|_| 0) }

Стратегия 4 - решение принимает вызывающая функция

Вместо возврата числа, возвращаем тип Option<число> - в зависимости от успешности функции, в нём будет либо само число, либо None:

fn to_int(s: &str) -> Option<i32> {  
    s.parse().ok() // ok() конвертирует Result<T,E> в Option<T> 

И тогда вызывающая функция должна обработать результат:

fn sum_str_vec (strs: Vec<String>) -> String {  
    let mut accum = 0i32;  
    for s in strs {  
        accum += match to_int(&s) {  
            Some(v) => v,  
            None => {  
                println!("Error converting a value, skipped");  
                0  // вернётся 0 +в лог пойдёт запись о пропуске
            }, }  }  
    return accum.to_string();  
}

Более короткий вариант записи через if let:

fn sum_str_vec (strs: Vec<String>) -> String {  
    let mut accum = 0i32;  
    for s in strs {  
        if let Some(val) = to_int(&s) {  
            accum += val;  
        } else { println!("Error converting a value, skipped"); }  
    }  
    return accum.to_string();  
}

Тип Option<T> также имеет метод unwarp_or(), отсюда ещё вариант записи:

fn sum_str_vec (strs: Vec<String>) -> String {  
    let mut accum = 0i32;  
    for s in strs {  
        accum += to_int(&s).unwrap_or(0); // раскрываем Option<T> 
    }  
    return accum.to_string();  
}

Стратегия 5 - в случае проблем, передать всё в основную программу

Вместо передачи значения из функции, в случае каких-либо проблем, мы возвращаем None:

fn sum_str_vec (strs: Vec<String>) -> Option<String> {  
    let mut accum = 0i32;  
    for s in strs {  
        accum += to_int(&s)?;  // в случае None, ? передаёт его далее на выход
    }                         
    Some(accum.to_string())    // на выход пойдёт значение или None
}

Стратегия 6 - передать всё в основную программу с объяснением ошибки

Мы возвращаем проблему в основную программу с объясением проблемы. Для этого заводим структуру под ошибку, и передаём уже не объект Option<T>, а объект Result<T,E>, где E = SummationError. Для такого объекта есть метод ok_or(), который либо передаёт значение, либо передаёт ошибку нужного типа:

#[derive(Debug)]  
struct SummationError;

fn sum_str_vec (strs: Vec<String>) -> Result<String, SummationError> {  
    let mut accum = 0i32;  
    for s in strs {  
        accum += to_int(&s).ok_or(SummationError)?;  
    }  
    Ok(accum.to_string())  
}

Вместо выдумывать свой собственный тип и конверстировать вывод метода parse() из Result<T,E> в Option<T>, а потом обратно, можно сразу протащить ошибку в объекте Result<T,E> в главную программу:

use std::num::ParseIntError;  // тип ошибки берём из библиотеки
  
fn to_int(s: &str) -> Result<i32, ParseIntError> {  
    s.parse()  // parse возвращает просто Result<T,E>
}  
  
fn sum_str_vec (strs: Vec<String>) -> Result<String, ParseIntError> {  
    let mut accum = 0i32;  
    for s in strs {  
        accum += to_int(&s)?; }  // ? передаёт ошибку нужного типа далее
    Ok(accum.to_string())  
}

Однако, мы хотим скрыть подробности работы и ошиби от главной програмы и передать ей ошибку в понятном виде, без разъяснения деталей её возникновения. Для этого можно сделать трансляцию ошибки из библиотечной в собственный типа, и далее передать методом map_err():

use std::num::ParseIntError;  
  
#[derive(Debug)]  
struct SummationError;  
  
fn to_int(s: &str) -> Result<i32, ParseIntError> {  
    s.parse()  
}  
  
fn sum_str_vec (strs: Vec<String>) -> Result<String, SummationError> {  
    let mut accum = 0i32;  
    for s in strs {  
        accum += to_int(&s).map_err(|_| SummationError)?; // конвертация ошибки  
    }                                                     // перед передачей
    Ok(accum.to_string())  
}

Где можно использовать ператор ?

Оператор ? можно использовать только в функциях для возврата совместимых значений типа Result<T,E>, Option<T> или иных данных со свойством FromResidual. Для работы такого возврата в заголовке функции должен быть прописан возврат нужного типа данных.
При использовании ? на выражении типа Result<T,E> или Option<T>, ошибка Err(e) или None будет возвращена рано из функции, а в случае успеха - выражение вернёт результат, и функция продолжит работу.

Пример функции, которая возвращает последний символ 1ой строки текста:

fn last_char_of_first_line(text: &str) -> Option<char> {
    text.lines().next()?.chars().last()
} // lines() возвращает итератор на текст
  // next() берёт первую строку текста. Если текст пустой - сразу возвращаем None

Files

Чтение из файлов

Для чтения файлов, нужно сначала добавить несколько методов из библиотек: FIle - открытие файлов и Read - чтение, который входит в prelude.

se std::fs::File;         // импорт File
use std::io::prelude::*;  // импорт Read
  
fn main() {  
    // читаемый файл находится в корне проекта.
    let mut file = File::open("access.log.10").expect("File not opened!");  
  
    let mut contents = String::new(); // закачать файл в переменную-строку
    file.read_to_string(&mut contents).expect("Cannot read file!");  
  
    println!("File contents: \n\n{}",contents);  
}

 Запись в файлы

use std::fs::File;  
use std::io::prelude::*;  
  
fn main() {  
    let mut file = File::create("report.log").expect("File not created!");  
  
    file.write_all(b"report.log");  
}

Flow Control

IF-ELSE

В Rust есть управление потоком программы через конструкции IF, ELSE IF, ELSE:

let test_number = 6;

if test_number % 4 == 0 {
println!("Divisible by 4");
} else if test_number % 3 == 0 { // Проверка останавливается на первом 
println!("Divisible by 3");      // выполнимом условии, дальнейшие проверки
} else if test_number % 2 == 0 { // пропускаются.
println!("Divisible by 2");
} else {
println!("Number is not divisible by 4, 3 or 2");
}

Конструкция IF является выражением (expression), поэтому можно делать присваивание:

let condition = true;

let number = if condition { "aa" } else { "ab" }; // присваивание результата IF
println!("Number is {number}");

LOOPS

Три варианта организовать цикл: через операторы loop, while, for.

LOOP - организация вечных циклов. Конструкция LOOP является выражением (expression), поэтому можно делать присваивание.

    let mut counter = 0;
    let result = loop {        
        counter += 1;
        if counter == 10 {
            break counter * 2; // выход из вечного цикла
        }
    }; // ";" нужно, т.к. было выражение
    println!("The result is {result}");

Если делать вложенные циклы, то можно помечать их меткой, чтобы выходить с break на нужный уровень.

    let mut count = 0;
    'counting_up: loop {            // метка внешнего цикла
        println!("count = {count}");
        let mut remaining = 10;

        loop {
            println!("remaining = {remaining}");
            if remaining == 9 {
                break;
            }
            if count == 2 {
                break 'counting_up;  // goto метка
            }
            remaining -= 1;
        }

        count += 1;
    }
    println!("End count = {count}");

WHILE - цикл с условием. Можно реализовать через loop, но с while сильно короче.

    let mut number = 10;
    while number != 0 {
        println!("{number}!");
        number -= 1;
    }
    println!("ЗАПУСК!");

FOR - цикл по множествам элементов. В том числе можно задать подмножество чисел.

for i in (1..10).rev() { // .rev() - выдача подмножества в обратную сторону
println!("Value: {i}");
}
println!("ЗАПУСК!");

Generics

Usage

Generics нужны для того, чтобы не повторять один и тот-же код несколько раз для разных типов данных. Это один из трёх основных способов экономии на повторении кода наравне с макросами и интерфейсами traits. Пример: нужно сортировать 2 набора данных - массив чисел, массив символов.

fn largest_i32(n_list: &[i32]) -> &i32 {
    let mut largest_num = &n_list[0];
    for i in n_list {
        if largest_num < i {
            largest_num = i;
        }
    }
    largest_num
}

fn largest_char(n_list: &[char]) -> &char {
    let mut largest_char = &n_list[0];
    for i in n_list {
        if largest_char < i {
            largest_char = i;
        }
    }
    largest_char

Вместо того, чтобы писать 2 почти идентичные функции под 2 типа данных, можно объединить оба типа в 1 функцию, указав “неопределённый тип”.

Note

Нужно учесть, что не все типы данных имеют возможность сравнения объектов между собой, возможность выстроить их в порядок (Order). Поэтому в примере ниже надо дополнить тип в заголовке интерфейсом-свойством (trait) порядка.

fn largest_universal<T: std::cmp::PartialOrd>(list: &[T]) -> &T {  
    // <T> = неопределённый тип, со свойством упорядочивания PartialOrd
    let mut largest = &list[0];  
  
    for item in list {  
        if largest < item {  
            largest = item;  
        }  
    }  
    largest  
}

fn main() {  
    let num_list = vec![11, 6, 33, 56, 13];//упорядочить числа 
    println!("Largest number: {}", largest_universal(&num_list));  
  
    let char_list = vec!['y','m','a','q'];//упорядочить символы в той же функции  
    println!("Largest char: {}", largest_universal(&char_list));
}

Структуры с неопределёнными типами

Можно создавать структуры, тип данных которых заранее неопределён. Причём в одной структуре можно сделать несколько разных типов.

struct Point<T,U> {  // <T> и <U> - 2 разных типа
    x: T,  
    y: U,  
}

let integer = Point{x:5,y:6};  // в оба поля пишем числа типа i32
let float_int = Point{x:1,y:4.2}; // в поля пишем разные типы i32 и f32

Аналогично неопределённые типы можно делать в перечислениях:

enum Option<T> {
    Some(T), // <T> - возможное значение любого типа, которое может быть или нет
    None,
}

enum Result<T, E> {
    Ok(T), // T - тип результата успешной операции
    Err(E), // Е - тип ошибки
}

Методы со структурами с неопределёнными типами

Можно прописать методы над данными неопределённого типа. Важно в заголовке метода указывать при этом неопределённый тип, чтобы помочь компилятору отделить его от обычного типа данных. Можно при этом сделать реализацию для конкретных типов данных, например, расчёт расстояния до точки от центра координат будет работать только для чисел с плавающей точкой. Для других типов данных метод расчёта работать не будет:

struct Point<T> {  
    x: T,  
    y: T,  
}  
  
impl<T> Point<T> {  // указываем неопределённый тип в заголовке
    fn show_x(&self) -> &T {  // метод возвращает поле данных
        &self.x  
    } }

impl Point<f32> {  // указываем конкретный тип float в методе, 
// чтобы только для него реализовать расчёт. Для не-float метод не будет работать
    fn distance_from_origin(&self) -> f32 {  
        (self.x.powi(2) + self.y.powi(2)).sqrt()  
    } }
  
fn main() {  
    let p = Point{x:5,y:6};  
    println!("P.x = {}",p.show_x()); // вызов метода для экземпляра p
}
Tip

Код с неопределёнными типами не замедляет производительность, потому что компилятор проходит и подставляет конкретные типы везде, где видит generics (“monomorphization” of code).

Hashmaps

Hashmap<K, V>

Это изменяемая структура словарь (“dictionary” в Python), которая хранит пары “ключ->значение”. В Rust Prelude она не входит, макроса создания не имеет. Поэтому нужно указывать библиотеку явно и явно создавать структуру.

use std::collections::HashMap;  
  
fn main() {  
    let mut scores = HashMap::new();  
    scores.insert(String::from("Alpha"),1);  
    scores.insert(String::from("Beta"),2);  
    scores.insert(String::from("Gamma"),3); 
}

Все ключи Hashmap должны быть уникальны и одного типа, все значения должны быть одного типа.

Warning

Значения с кучи типа String перемещаются (move) в Hashmap, который становится их владельцем.

Взятие значения по ключу из Hashmap с помощью get нужно сопровождать проверкой - есть ли в памяти запрашиваемые ключ и значение:

let name = String::from("Gamma");  
if let Some(letter_num) = scores.get(&name) {  
    println!("{}",letter_num);  
} else { println!("Value not in HashMap!"); }

Итерация по Hashmap похожа на итерацию по вектору:

for (key, value) in &scores {  
    println!("{key} -> {value}"); }

Обновление Hashmap

Есть ряд стратегий обновления значений в Hashmap:

  • Перезаписывать всегда
scores.insert(String::from("Gamma"),3); // вставка дважды значений по одному 
scores.insert(String::from("Gamma"),6); // ключу сохранит последнее значение
  • Записывать значение, если у ключа его нет
scores.entry(String::from("Delta")).or_insert(4); // entry проверяет наличие
// значения, or_insert возвращает mut ссылку на него, либо записывает новое 
// значение и возвращает mut ссылку на это значение.
  • Поднимать старое значение ключа, проверять его перед перезаписью
let text = "hello world wonderful world";  
let mut map = HashMap::new();  
  
for word in text.split_whitespace() {  
    let count = map.entry(word).or_insert(0);  
    *count += 1;  
}  

println!("{:?}",map); // {"wonderful": 1, "hello": 1, "world": 2}

Modules Structure

External link: https://doc.rust-lang.org/stable/book/ch07-02-defining-modules-to-control-scope-and-privacy.html

Правила работы с модулями

  • Crate Root. При компиляции crate, компилятор ищет корень: src/lib.rs для библиотеки, либо src/main.rs для запускаемого файла (binary);
  • Модули. При декларировании модуля в crate root, например, mod test, компилятор будет искать его код в одном из мест:
    • Сразу после объявления в том же файле (inline);
    • В файле src/test.rs;
    • В файл src/test/mod.rs - старый стиль, поддерживается, но не рекомендуется.
  • Подмодули. В любом файле, кроме crate root, можно объявить подмодуль, например, mod automa. Компилятор будет искать код подмодуля в одном из мест:
    • Сразу после объявления в том же файле (inline);
    • В файле src/test/automa.rs;
    • В файле src/test/automa/mod.rs - *старый стиль, поддерживается, но не рекомендуется.
  • Путь до кода. Когда модуль часть crate, можно обращаться к его коду из любого места этой crate в соответствии с правилами privacy и указывая путь до кода. Например, путь до типа robot в подмодуле automa будет crate::test::automa::robot;
  • Private/public. Код модуля скрыт от родительских модулей по умолчанию. Для его публикации нужно объявлять его с pub mod вместо mod;
  • Use. Ключевое слово use нужно для сокращения написания пути до кода: use crate::test::automa::robot позволяет далее писать просто robot для обращения к данным этого типа.
Note

Нужно лишь единожды внести внешний файл с помощью mod в дереве модулей, чтобы он стал частью проекта, и чтобы другие файлы могли на него ссылаться. В каждом файле с помощью mod не надо ссылаться, mod - это НЕ include из Python и других языков программирования.

Пример структуры

my_crate
├── Cargo.lock
├── Cargo.toml
└── src
    ├── test
    │   └── automa.rs
    ├── test.rs
    └── main.rs

Вложенные модули

Для примера, возьмём библиотеку, обеспечивающую работу ресторана. Ресторан делится на части front - обслуживание посетителей, и back - кухня, мойка, бухгалтерия.

mod front {
    mod hosting {
        fn add_to_waitlist() {}
        fn seat_at_table() {}
    }

    mod serving {
        fn take_order() {}
        fn serve_order() {}
        fn take_payment() {}
    }
}

Пример обращения к функции вложенного модуля

Объявление модуля публичным с помощью pub не делает его функции публичными. Нужно указывать публичность каждой функции по отдельности:

mod front_of_house {
    pub mod hosting { // модуль публичен, чтобы к нему обращаться
        pub fn add_to_waitlist() {} // функция явно публична
        // несмотря на публичность модуля, к функции обратиться нельзя
        // если она непублична
    }
}

pub fn eat_at_restaurant() {
    // Абсолютный путь через корень - ключевое слово crate
    crate::front_of_house::hosting::add_to_waitlist();

    // Относительный путь
    front_of_house::hosting::add_to_waitlist();
}

Обращние к функции выше уровнем

Относительный вызов функции можно сделать через super (аналог “..” в файловой системе):

fn deliver_order() {}

mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        super::deliver_order(); // вызов функции в родительском модуле
    }
    fn cook_order() {}
}

Обращение к структурам и перечислениям

Поля структур приватны по умолчанию. Обозначение структуры публичной с pub не делает её поля публичными - каждое поле нужно делать публичным по отдельности.

mod back_of_house {
    pub struct Breakfast {      // структура обозначена как публичная
        pub toast: String,      // поле обозначено публичным
        seasonal_fruit: String, // поле осталось приватным
    }

    impl Breakfast {
        pub fn summer(toast: &str) -> Breakfast {
            Breakfast {
                toast: String::from(toast),
                seasonal_fruit: String::from("peaches"),
    } } } }

pub fn eat_at_restaurant() {
    // Обращение к функции. Без функции к структуре с приватным полем 
    // не получится обратиться:
    let mut meal = back_of_house::Breakfast::summer("Rye");
    // запись в публичное поле:
    meal.toast = String::from("Wheat");
    println!("I'd like {} toast please", meal.toast);

    // если раскомменировать строку далее, будет ошибка компиляции:
    // meal.seasonal_fruit = String::from("blueberries");
}

Поля перечислений публичны по умолчанию. Достатоно сделать само перечисление публичным pub enum, чтобы видеть все его поля.

mod back_of_house {
    pub enum Appetizer { // обозначаем перечисление публичным
        Soup,
        Salad,
    } }

pub fn eat_at_restaurant() {
    let order1 = back_of_house::Appetizer::Soup;
    let order2 = back_of_house::Appetizer::Salad;
}

Обращение к объектам под другим именем

С помощью as можно создать ярлык на объект в строке с use. Особенно это удобно в случае, когда нужно обратиться к одинаковым по имени объектам в разных модулях:

use std::fmt::Result;
use std::io::Result as IoResult; // IoResult - ярлык на тип Result в модуле io

fn function1() -> Result {
    // ... }

fn function2() -> IoResult<()> {
    // ... }

Ре-экспорт объектов

При обращении к объекту с помощью use, сам ярлык этот становится приватным - через него могут обращаться только функции текущего scope. Для того, чтобы из других модулей функции могли тоже обратиться через этот ярлык, нужно сделать его публичным:

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    } }

pub use crate::front_of_house::hosting; // ре-экспорт объекта

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist(); // обращение к функции через ярлык
}

Работа с внешними библиотеками

Внешние библиотеки включаются в файл Cargo.toml. Далее, публичные объекты из них заносятся в scope с помощью use.

# Cargo.toml
rand = "0.8.5"
use rand::Rng;
fn main() {
    let secret_number = rand::thread_rng().gen_range(1..=100);
}

Если нужно внести несколько объектов из одной библиотеки, то можно сократить количество use:

//use std::cmp::Ordering;
//use std::io;
use std::{cmp::Ordering, io}; // список объектов от общего корня

//use std::io;
//use std::io::Write;
use std::io{self, Write}; // включение самого общего корня в scope

use std::collections::*; // включение всех публичных объектов по пути
Warning

Следует быть осторожным с оператором glob - *, так как про внесённые с его помощью объекты сложно сказать, где именно они были определены.

Ownership and References

Ownership

Объявленная переменная, обеспеченная памятью кучи (heap) - общей памятью (не стека!) всегда имеет владельца. При передаче такой переменной в другую переменную, либо в функцию, происходит перемещение указателя на переменную = смена владельца. После перемещения, нельзя обращаться к исходной переменной.

let s1 = String::from("hello"); // строка в куче создана из литералов в стеке
let s2 = s1;                    // перемещение
println!("{}, world!", s1);     // ошибка! Вызов перемещённой переменной

Решения:

  • Можно сделать явный клон переменной со значением;
    let s1 = String::from("hello");
    let s2 = s1.clone();                  // полный клон. Медленно и затратно,
    println!("s1 = {}, s2 = {}", s1, s2); // но нет передачи владения
  • Передавать ссылку на указатель. Ссылка на указатель - ‘&’, раскрыть ссылку на указатель - ‘*’.
fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);      // передача ссылки на указатель
    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize { // приём ссылки на указатель
    s.len()
}

References

Для внесения изменений по ссылке на указатель, нужно указать это явно через ‘mut’.

fn main() {
    let mut s = String::with_capacity(32); // объявить размер блока данных заранее, чтобы потом не довыделять при закидывании данных в строку = быстрее
    change(&mut s); // передача изменяемой ссылки
}

fn change(some_string: &mut String) { // приём изменяемой ссылки на указатель
    some_string.push_str("hello, world");
}
Tip

Правила:

  1. В области жизни может быть лишь одна изменяемая ссылка на указатель (нельзя одновременно нескольким потокам писать в одну область памяти);
  2. Если есть изменяемая ссылка на указатель переменной, не может быть неизменяемых ссылок на указатель этой же переменной (иначе можно перезаписать данные в процессе их же чтения);
  3. Если ссылка на указатель переменной неизменяемая, можно делать сколько угодно неизменяемых ссылок на указатель (можно вместе читать одни и те же данные);
  4. Конец жизни ссылки определяется её последним использованием. Можно объявлять новую ссылку на указатель, если последняя изменяемая ссылка по ходу программы более не вызывается.
let mut s = String::from("hello");
{
    let r1 = &mut s;
} // r1 вышла из области жизни, поэтому можно объявить новую ссылку на указатель.
    let r2 = &mut s;

Strings

Статья по ссылкам на память в Rust

String

Тип данных с владельцем. Имеет изменяемый размер, неизвестный в момент компиляции. Представляет собой векторную структуру:

pub struct String {
    vec: Vec<u8>;
}

Поскольку структура содержит Vec, это значит, что есть указатель на массив памяти, размер строки size структуры и ёмкость capacity (сколько можно добавить к строке перед дополнительным выделением памяти под строку).

&String

Ссылка на String. Не имеет владельца, размер фиксирован, известен в момент компиляции.

str

Набор символов (литералов), размещённых на стеке. Не имеет владельца, размер фиксирован, известен в момент компиляции. Можно превращать str в String через признак from:

let text = String::from("TEST"); // "TEST" :str

&str

Ссылка на часть, slice от String (куча), str (стек) или статической константы. Не имеет владельца, размер фиксирован, известен в момент компиляции.

  • &String можно неявно превращать в &str;
  • &str нельзя неявно превращать в &String.
fn main() {
    let s = "hello_world";
    let mut mut_string = String::from("hello");
    success(&mutable_string);
    fail(s);
}

fn success(data: &str) { // неявный перевод &String -> &str
    println!("{}", data);
}

fn fail(data: &String) { // ОШИБКА - expected &String, but found &str
    println!("{}", data);
}
Warning

Пока существует &str её в области жизни нельзя менять содержимое памяти, на которое она ссылается, даже владельцем строки.

Примеры применения

Строковые константы

const CONST_STRING: &'static str = "a constant string"; 

Изменение строк

При наличии String, нужно передать ссылку &mut String для изменения:

fn main() {
 let mut mutable_string = String::from("hello ");
 do_mutation(&mut mutable_string);
 println!("{}", mutables_string); // hello world!
}

fn do_mutation(input: &mut String) {
 input.push_str("world!");
}

Строки с владением

Получение String с передачей владения нужно при получении строки от функции, передача в поток (thread):

fn main() {
    let s = "hello_world";
    println!("{}", do_upper(s)); // HELLO_WORLD
}

fn do_upper(input: &str) -> String { // возврат String
    input.to_ascii_uppercase()
}

Отображение части строки

Передавать владельца не нужно, передаём в &str:

let s = String::from("Hello World!");
let word = first_word(&s);
println!("The first word is: {}", word);
}

fn first_word(s: &String) -> &str { // передача строки по ссылке
let word_count = s.as_bytes();

for (i, &item) in word_count.iter().enumerate() {
    if item == b' ' {
    return &s[..i]; // возврат части строки как &str
    }
}
&s[..]  // обязательно указать возвращаемое значение, если условие в цикле выше ничего не вернёт (например, строка не содержит пробелов = вернуть всю строку)

Можно пройти по строке итератором chars() и его методами взятия N-го символа nth() спереди или nth_back() сзади:

let person_name = String::from("Alice");  
println!("The last character of string is: {}", match person_name.chars().nth_back(0) {  // ищем 1-ый символ с конца строки
        Some(i) => i.to_string(),  // если находим - превращаем в строку
        None => "Nothing found!".to_string(),  // не находим = сообщаем
    });  

Вывод строк

  • Макрос println! позволяет вывести строку в поток stdout;
// println!("Hello there!\n"); 
// раскрывается в такой код:
use std::io::{self, Write};
io::stdout().lock().write_all(b"Hello there!\n").unwrap();
  • Макрос format! позволяет сформировать строку и вернуть из функции;
  • Метод len() выдаёт длину строки;
  • Метод is_empty() проверят, что строка непустая;
  • Метод contains() ищет одну строку в другой строке;
  • Метод replace(from,to) заменяет часть строки на другую и выдаёт результат;
  • Метод splt_whitespace() позволяет делить строку на части по пробелам;
  • Метод push_str() позволяет добавить текст к строке (строка должна быть mut).
fn main() {    
    let mut a = String::from("Wonderful RUST World");  
        println!("Hello{}!", output_string(&a));  // вывод строки  
        println!("String is empty? {}", a.is_empty());  
        println!("String length: {}", a.len());  
        println!("Does string contain 'Hello'? {}", a.contains("Hello")); 
        println!("{}",a.replace("RUST","Python")); // Wonderful Python World
  
        for i in a.split_whitespace() {  
            println!("{}", i);  
        }  
      
     a.push_str(" And let's go!");  
     println!("{}",a);

}    
    
fn output_string(t: &String) -> String {  
    format!(", {}",t)   // возврат сформированной строки  
}

Структуры

Если структуре надо владеть своими данными - использовать String. Если нет, можно использовать &str, но нужно указать время жизни (lifetime), чтобы структура не пережила взятую ей строку:

struct Owned {
    bla: String,
}

struct Borrowed<'a> {
    bla: &'a str,
}

fn main() {
    let o = Owned {
        bla: String::from("bla"),
    };
    let b = create_something(&o.bla);
}

fn create_something(other_bla: &str) -> Borrowed {
    let b = Borrowed { bla: other_bla };
    b // при возврате Borrowed, переменная всё ещё в области действия!
}

Гласные / согласные буквы

Проверку нужно написать в виде функции:

fn is_vowel(c: char) -> bool {  
    c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' ||  
    c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U' }

let text = String::from("Aria");

Разворот слов

Дана строка с пробелами меду словами. Необходимо развернуть слова в строке наоборот, при этом сохранить пробелы.

fn reverse_words_split(str: &str) -> String {  
    str.to_string()
    .split(" ") // при разделении split() множественные пробелы сохраняются
    .map(|x| x.chars().rev().collect::<String>()) // разворот слов
    .collect::<Vec<String>>().                    // сбор всего в вектор
    .join(" ")                                    // превращение вектора в строку
}

fn main() {  
    let word: &str = "The   quick brown fox jumps over the lazy dog.";  
    println!("{}",reverse_words_split(&word));  
}

// ehT   kciuq nworb xof spmuj revo eht yzal .god

Structures

Struct Data Type

Struct - комплексный изменяемый тип данных, размещается в куче (heap), содержит внутри себя разные типы данных. Он похож на кортеж (tuple), однако типы данных должны иметь явные именования.

struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64, // запятая в конце обязательна
}

Можно передать struct в функцию или вернуть из функции:

fn main() {
// создаём изменяяемый объект по структуре данных выше
let mut user1 = create_user(String::from("john@doe.com"), String::from("testuser")); 

println!("User Email is {}", user1.email);
user1.email = String::from("Parker@doe.com");
println!("User Email is {}", user1.email);
}

fn create_user(email: String, username: String) -> User { // возврат из функции
User {
active: true,
username,  
email,
// имена полей имеют с входными переменными, и можно не писать username: username, email: email.
sign_in_count: 1,
} // return заменяется отсутствием знака ";"" как обычно 
}

Updating Structs

Если нужно создать новую структуру по подобию старой, и большая часть полей у них похожи, то можно использовать синтаксический сахар:

let user2 = User {
email: String::from("another@example.com"), // задать новое значение поля
..user1 // взять остальные атрибуты из user1. Идёт последней записью
};

Tuple structs

Структуры такого вида похожи на кортежи, но имеют имя структуры и тип. Нужны, когда нужно выделить кортеж отдельным типом, либо когда надо дать общее имя кортежу. При этом отдельные поля не имеют имён.

struct Color (i32, i32, i32);
struct Point (i32, i32, i32);

fn main() {
let red = Color(255,0,0);
let origin = Point(0, 0, 0);

Переменные red и origin разных типов. Функции, которые берут Color как параметр, не возьмут Point, несмотря на одинаковые типы внутри. Каждая структура = свой собственный тип. Разбор таких структур на элементы аналогичен кортежам.

let (x,y,z) = (origin.0,origin.1,origin.2);

Unit-like structs

Структуры без полей аналогичны кортежам без полей, только с именем.

struct TestTrait;

fn main() {
 test = TestTrait;
}

Такие структуры нужны для задания признаков (traits), когда в самой структуре данные хранить не нужно.

Структурные признаки

Можно выводить информацию о содержимом полей структуры для анализа кода. Для этого нужно добавить над структурой пометку debug:

#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}

fn main() {
  let scale = 2;
  let rect = Rectangle {
  width: dbg!(20 * scale), // вывод поля структуры. dbg! возвращает назад
  height: 10,              // взятое значение, с ним далее можно работать
  };
  println!("Rectangle content: {:?}",rect); // вывод содержимого структуры
  dbg!(&rect); // ещё вариант вывода - в поток stderr. Функция dbg! 
               // забирает владение структурой, поэтому передача по ссылке
}

Структурные методы

Можно добавлять функции как методы, привязанные к структурам. Это позволяет организовать код более чётко - по объектам и действиям над ними.

struct Rectangle {
width: u32,
height: u32,
}

impl Rectangle {  // impl определяет блок методов структуры Rectangle
fn area(&self, scale) -> u32 { // 1-ый параметр всегда self = структура
 self.width * self.height * scale // тело функции и возврат значения
} }

fn main() {
  let rect = Rectangle {
  width: 20,
  height: 10,
  };
  println!("Rectangle area is {}", rect.area(2)); // вызов метода
}

Как и везде, для внесения изменений в объект структуры, в блоке методов можно объявить &mut self, а для перемещения владения - просто self. Это нужно изредка при превращении self в другой вид объекта, с целью запретить вызов предыдущей версии объекта. Блоков impl может быть несколько.

Асоциированные функции

В блоке методов impl можно добавлять функции, которые первым параметром не берут саму структуру self. Такие функции не являются методами и часто служат для создания новых версий объекта.

fn square(side: u32) -> Self { // Self - алиас для типа данных Rectangle
  Self {
    width: side,
    height: side,
} } }

fn main() {
  let sq = Rectangle::square(10); // вызов асоциированной функции через ::
  println!("Square created from {:?}",sq);
}

Создание типа данных с проверками

Вместо проверять введённые данные на корректность внутри функций, можно объявить собственный тип данных, содержащий в себе все необходимые проверки. Например, объявим число от 1 до 100 для игры, где надо угадать число:

pub struct Guess { // объявили тип данных (публичный)
    value: i32,    // внутри число (приватное)
}

impl Guess {
    pub fn new(value: i32) -> Guess { // метод new проверяет значение
        if value < 1 || value > 100 { // на заданные границы 1-100
            panic!("Guess value must be between 1 and 100, got {}.", value);
        }
        Guess { value } // возврат нового типа данных
    }

    pub fn value(&self) -> i32 { // метод getter для получения значения value
        self.value               // он нужен, тк напрямую видеть value нельзя
    }                            // Это приватная переменная в структуре.
}

Traits

Инициализация типажа

Типаж нужен для организации интерфейса: он задаёт ограничения-особенности поведения для переменных или структур с неопределёнными (generic) переменными. Мы отделяем объявление типажа от его реализации. При объявлении типажа можно оставить обязательную реализацию на потом, либо вписать реализацию функций в типаже по-умолчанию:

// типаж задаёт метод и ограничения по входным/выходным типам
trait LandVehicle {  
    fn LandDrive(&self) -> String; }  

// типаж задаёт методы плюс их реализация по умолчанию
trait WaterVehicle {  
    fn WaterDrive(&self) { println!("Default float"); }  
}

Применение типажей к структурам данных

Во время применения, если реализация по умолчанию была задана, то можно её переделать под конкретную структуру, либо использовать эту реализацию:

struct Sedan {}  
struct Rocketship {}  

// типаж LandVehicle не имеет реализации по умолчанию, реализуем тут
impl LandVehicle for Sedan {  
    fn LandDrive(&self) -> String { format!("Car zoom-zoom!") } }

// типаж WaterVehicle имеет выше реализацию по умолчанию, используем её
impl WaterVehicle for Rocketship {}

Объединение типажей

При объединении, создаётся ярлык (alias). При этом каждый входящий в него типаж нужно отдельно применить к структуре данных. При этом можно также использовать реализацию определённых в типаже методов по умолчанию, либо написать свою.

// создание ярлыка
trait AmphibiousVehicle: LandVehicle + WaterVehicle {}

// применение типажей к структуре
impl AmphibiousVehicle for Carrier {}  
impl LandVehicle for Carrier {  
    fn LandDrive(&self) -> String { format!("Use air thrust to travel on land") }  
}  
impl WaterVehicle for Carrier {}

Вызов методов экземпляра структуры определённого типажа

fn main() {  
    let toyota_camry = Sedan {};  
    println!("{}",toyota_camry.LandDrive());
  
    let rs = Rocketship {};  
    rs.WaterDrive();  
  
    let project_x = Carrier {};  
    println!("{}",project_x.LandDrive());  
    project_x.WaterDrive();  
}

Variables and constants

Note

When in doubt on variable type: just use i32 for everything! i32 is the default in Rust

Mutablitity

By default, variables in Rust are immutable. To make a variable mutable, special “mut” identifier must be placed. Rust compiler may infer the variable type from the type of value.

let x = 5; // immutable variable, type i32 guessed by Rust as default for numbers.

let mut x = 5; // mutable variable

Shadowing

Variable names can be reused. This is not mutability, because shadowing always re-creates the variable from scratch. Previous variable value may be used:

let x = 5; 
let x = x + 1; // new variable created with value = 6

Constants

Constant values are always immutable and available in the scope they were created in throughout the whole program. Type of constant must always be defined. Constants may contain results of operations. They are evaluated by Rust compiler. List of evaluations: https://doc.rust-lang.org/stable/reference/const_eval.html

const ONE_DAY_IN_SECONDS: u32 = 24 * 60 * 60; // type u32 MUST be defined

let phrase = "Hello World";
println!("Before: {phrase}"); // Before: Hello World

let phrase = phrase.len();
println!("After: {phrase}"); // After: 11

Compound variables

Tuple

Compound type, immutable, consists of different types.

let tup: (u32, f32, i32) = (10, 1.2, -32);
let (x,y,z) = tup; // tuple deconstructing into variables
let a1 = tup.0;
let a2 = tup.1; // another way to deconstruct values

Deconstructing tuples is very useful when a function returns a tuple:

let (left, right) = slice.split_at(middle);
let (_, right) = slice.split_at(middle); // use '_' to throw away part of return

Array

Compound type, mutable, all values of the same type.

let ar: [i32;5] = [1,2,3,4,5]; 
// array data is allocated on the stack rather than the heap
// [i32;5] - type of values and number of elements
let first = ar[0]; 
let second = ar[1]; // accessing array elements

Подсчёт количества одинаковых элементов в массиве. Требует подключить библиотеку itertools:

use itertools::Itertools;

fn main() {  
    let number_list = [1,12,3,1,5,2,7,8,7,8,2,3,12,7,7];
    let mode = number_list.iter().counts(); // Itertools::counts() 
    // возвращает Hashmap, где ключи взяты из массива, значения - частота
    for (key, value) in &mode {  
        println!("Число {key} встречается {value} раз");  
    }  }

Vectors

Vectors

Вектор - множество данных одного типа, количество которых можно изменять: добавлять и удалять элементы. Нужен, когда:

  • требуется собрать набор элементов для обработки в других местах;
  • нужно выставить элементы в определённом порядке, с добавлением новых элементов в конец;
  • нужно реализовать стэк;
  • нужен массив изменяемой величины и расположенный в куче.
// Задание пустого вектора:
// let mut a test_vector: Vec<i32> = Vec::new();  

// Задание вектора со значениями через макрос:
let mut test_vector = vec![1, 2, 3, 4];  

test_vector.push(42);  // добавить число 42 в конец mut вектора
test_vector.remove(0);  // удалить первый элемент =1
  
for i in &mut test_vector {  // пройти вектор как итератор для вывода
*i += 1; // изменять значения при их получении требует делать '*' dereference
println!("{i}"); }

Получение элемента вектора

Элемент можно получить с помощью индекса, либо с помощью метода get:

let mut test_vector = vec![1,2,3,4,5];  
  
println!("Third element of vector is: {}", &test_vector[2]);  // индекс
  
let third: Option<&i32> = test_vector.get(2);  // метод get
match third {  
    Some(third) => println!("Third element of vector is: {}", third),  
    None => println!("There is no third element")  
}

Разница в способах в реакции на попытку взять несуществующий элемент за пределами вектора. Взятие через индекс приведёт к панике и остановке программы. Взятие с помощью get сопровождается проверкой и обработкой ошибки.

Хранение элементов разных типов в векторе

Rust нужно заранее знать при компиляции, сколько нужно выделять памяти под каждый элемент. Если известны заранее все типы для хранения, то можно использовать промежуточный enum:

#[derive(Debug)]  
enum SpreadSheet {  
    Int(i32),  
    Float(f64),  
    Text(String)  
}  
  
fn main() {  
    let row = vec![  
      SpreadSheet::Int(42),  
      SpreadSheet::Float(3.14),  
      SpreadSheet::Text(String::from("red"))  
    ];  
  
    for i in row {  
        println!("{:?}",i);  
    }  }

Сортировка

let number_vector = vec!(1,12,3,1,5);   
number_vector.sort(); // 1,1,3,5,12

Конвертация

Конвертация из массива array в vector:

let number_list = [1,12,3,1,5,2];  
let number_vector = number_list.to_vec(); // перевод array[i32] -> vector<i32>
Chapter 2

Operations

Deploying & configuring complex systems.

  • Ansible

    Ansible installation, config & usage

  • Grafana Loki

    Grafana Loki installation, config & usage

Subsections of Operations

Ansible

Articles in section

Installation

Do not install Ansible from local OS repo - it is usually older in version there. Use latest Python pip repo to install a fresh version.

sudo apt install python3-distutils
wget http://bootstrap.pypa.io/get-pip.py
sudo python3 get-pip.py
sudo pip3 install ansible
ansible --version
Tip

Ansible will be the latest version supporting current Python in OS. So to get latest Ansible , Python must be updated as well!

Warning

Do not update the default Python in OS - it is used by system services, which may break!

Build Python:

sudo apt install build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libsqlite3-dev libreadline-dev libffi-dev curl libbz2-dev

wget https://www.python.org/ftp/python/3.9.15/Python-3.9.15.tgz

tar -xf Python-3.9.15.tgz

cd Python-3.9.15
./configure --enable-optimizations

make -j 8

Installing Python:

  • Best way is to do checkinstall, create a .deb file to share with the team.
  • Use altinstall parameter to install Python in an alternative path. This is simpler & better than using Python virtualenv.
sudo make altinstall # now Python 3.9x is installed on separate path, while Python 3.7 in OS is unchanged

sudo python3.9 -m pip install --upgrade pip

sudo python3.9 -m pip install ansible

ansible --version # now ansible is the latest version

Architecture

graph LR I[(Inventory)] --> R1(Role01); M(Modules) --> R1; I[(Inventory)] --> R2(Role02); M(Modules) --> R2; I[(Inventory)] --> RN(Role N); M(Modules) --> RN; R1 -->|include role| P(Playbook); R2 -->|include role| P(Playbook); RN -->|include role| P(Playbook); P --> C(Ansible Config); C --> Py(Python); Py-->|SSH|Client01; Py-->|SSH|Client02; Py-->|SSH|Client_N;

Folder Structure

video.yml - main program:

---
- import_playbook: playbooks/db.yml
- import_playbook: playbooks/front.yml
- import_playbook: playbooks/zoneminder.yml
...

front.yml - role example:

---
- name: front-end play
  hosts: all
  gather_facts: yes
  become: yes

  tasks:
    - name: include role apache
      include_role:
        name: apache

    - name: include role php
      include_role:
        name: php
...

apache / tasks / main.yml - tasks example:

---
- name: install apache
  apt:
  name: apache2

- name: Enable service apache2
  ansible.builtin.systemd:
    name: apache2
    enabled: yes
    masked: no

- name: Make sure apache2 is running
  ansible.builtin.systemd:
    state: started
    name: apache2
...

Role structure

Directories inside a role:

- defaults - variable values by default
-- main.yml

- vars - variables defined by role (for other roles)
-- main.yml

- tasks - jobs to be completed
-- main.yml

- handlers - actions to be taken after checks
-- main.yml

- files - static files to be copied into client machine
- templates

Subsections of Ansible

Inventory

Initialization

Create inventory file with IP addresses. Check computers in inventory by doing a ping:

ansible all -i inventory -u vagrant -m ping -k
# -i - <inventory file>
# -u - <username>
# -m - <select module>
# -k - <interactive password prompt>
ansible all -i inventory -u admin -m ping -k -vvv # debug mode with verbosity 3

Inventory examples:

[db]
vm ansible_host=192.168.1.98 ansible_user=aagern

[app]
vm ansible_host=192.168.1.98 ansible_user=aagern

[front]
vm ansible_host=192.168.1.98 ansible_user=aagern

[all:vars]
ansible_ssh_extra_args="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
ansible_python_interpreter=/usr/bin/python3
web1 ansible_ssh_host=192.168.33.20
db1 ansible_ssh_host=192.168.33.30

[webservers]
web1

[dbservers]
db1

[datacenter:children]
webservers
dbservers

[datacenter:vars]
ansible_ssh_user=vagrant
ansible_ssh_pass=vagrant

Configuration

Order of precedence for the config file:

  1. $ANSIBLE_CONFIG env variable  (first place to be searched)
  2. ./ansible.cfg - local file in the working directory
  3. ~/.ansible.cfg - user home directory
  4. /etc/ansible/ansible.cfg - global config (last place to be searched)

First config file found wins, Ansible stops looking for other config files.

Environment override

Specify: $ANSIBLE_

Override settings on the fly:

$ export ANSIBLE_FORKS=10

[defaults] forks - how many parallel processes does Ansible handle. Default=5, production recommended=20

host_key_checking - check host key before sending commands. Default=True (for production), for dev/test systems =False (easy control)

log_path - Ansible logging. Default=Null, produstion recommended - set path all Ansible users can write to.

Target patterns

Patterns to choose hosts/groups:

  • OR pattern: group1:group2

Example:

ansible webservers:dbservers -i inventory -m service -a "name=iptables state=stopped" --sudo
  • NOT pattern: :!group2
  • Wildcard pattern: web.lab.local*
  • REGEX pattern: ~web[0-9]+
  • AND pattern: group1:&group2 - apply to hosts BOTH in group1 and group2 (intersection)
  • Complex patterns

Complex pattern example: webservers:&production:!python3 # apply to web servers in production but not in the Python3 group

Modules

Help on modules

ansible-doc -l # all installed modules list
ansible-doc <module-name> # module man
ansible-doc -s <module-name> # playbook snippets code examples on module

Module examples

Copy module

  • Copies a file from the local box to a remote system - useful for copying config files to remote system
  • can do backups
  • can do remote validation

Fetch module

  • Copy a file from remote system to local box
  • validate file using md5 checksum

Setup module

  • Gather info and facts on remote system
  • Inventory analysis of the system
ansible -i inventory web1 -m setup # gather all available system info
ansible -i inventory web1 -m setup -a "filter=ansible_eth*" # gather info on NICs
ansible -i inventory all -m setup --tree ./setup # form an inventory of files in /setup/ folder with info on targeted systems

Apt module (for Debian systems) / Yum module (RedHat systems)

  • Install, update, delete packages
  • Can update entire system Example for Yum:
ansible webservers -i inventory -m yum -a "name=httpd state=present" -u vagrant --sudo
# name - name of package (Apache)
# present - if package is not there, install. If it is there - do nothing and report "changed: false" (idempotence test)

Service module

  • Start/stop/restart services
  • Set autostart option for services
ansible webservers -i inventory -m service -a "name=httpd state=started enabled=yes" -u vagrant --sudo

# name - name of service (Apache)
# state = started/stopped - make idempotence test and change if necessary
# enabled = yes/no - autostart on system boot

Playbooks

General tasks usage

# Tasks in a playbook are executed top down. Tasks use modules.
tasks:
  - name: Name the task for readability
    module: parameters=go_here

# Example:
  - name: Deploy Apache Configuration File
    copy: src=./ansible/files/configuration/httpd.conf

          dest=/etc/httpd/conf/

Playbook execution: ansible-playbook my_playbook.yml

Playbook example:

---
# -------- Global play declaration
- hosts: webservers    
  ## ----- Variables per play
  vars:
    git_repo: https://github.com/repo.git
    http_port: 8081
    db_name: wordpress

  ## ------------------------
  ### ---- Declare user to run tasks
  sudo: yes
  sudo_user: wordpress_user

  ### ------------------------------
  gather_facts: no # dont't gather facts with SETUP module (default gathers facts - expensive in time)
  remote_user: root
  tasks:

# --------------------------------
  - name: Install Apache
    yum: name=httpd state=present
  - name: Start Apache
    service: name=httpd state=started

Including files

Use “- include” and “- include_vars” directives to include playbook files:

tasks:
- include: wordpress.yaml
  vars:
    sitename: My Awesome Site
    
- include: reverse_proxy.yaml
- include_vars: variables.yaml

Register task output

Use the output of one task for another task:

tasks:
- shell: /usr/bin/whoami
  register: username

- file: path=/home/myfile.txt
  owner: {{ username }}

Debugging tasks with Debug module

Add screen output and print content of variables:

tasks:
  - debug: msg="This host is {{ inventory_hostname }} during execution"

  - shell: /usr/bin/whoami
    register: username
    
  - debug: var=username

Input during playbook execution

Promt user during execution:

- hosts: web1
  vars_prompt:
  - name: "sitename"
    prompt: "What is the new site name?"

  tasks:
    - debug: var=username

Playbook handlers

A handler can be informed to execute a task (restart service) only if state=changed.

  • Run after all tasks
  • Run only once no matter how many times they are called

Handlers syntax is the same as tasks syntax:

  tasks:
  - name: Copy Apache Config files
  - copy: src=./files/httpd.conf
          dest=/etc/httpd/config/

    notify:
      - Apache Restart

  handlers:
  - name: Apache Restart
    service: name=httpd state=restarted

“When” condition

Commence a task if condition is True:

tasks:
- name: Install Httpd Package
  apt: name=httpd state=present
  when: ansible_os_family == "RedHat"

- name: Install Apache2 Package
  yum: name=apache2 state=present
  when: ansible_os_family == "Debian"

Check output of previous task as condition to run next task:

tasks:
- name: Stop iptables now
  service: name=iptables state=stopped
  register: result
  ignore_errors: yes   # supress default stop on error

- debug: msg="Failure!"
  when: result|failed  # Debug message will only be shown if task has failed
                       # other conditions are "result|success", "result|skipped"

Checking variables with WHEN condition

Bring variable check to BOOL check:

- name: "test"
  hosts: local
  vars_prompt:
    - name: "os_type"
      prompt: "What OS? (centos or ubuntu)"
      default: "centos"
      private: no
  vars:
    - is_ubuntu: "{{os_type == 'ubuntu'}}"
    - is_debian: "{{os_type == 'debian'}}"
  tasks:
    - debug: msg="this shows the conditional if variable equals ubuntu"
      when: is_ubuntu|bool
      
    - debug: msg="this shows the conditional if variable equals centos"
      when: is_centos|bool

Grafana Loki

Installation

Create dirs for Loki:

cd ~
mkdir promtail
mkdir loki
mkdir grafana

Create a Docker-Compose file

vim docker-compose.yml

version: "3"

networks:
loki:

services:
loki:
image: grafana/loki:2.4.0
volumes:
- /home/aagern/loki:/etc/loki
ports:
- "3101:3100"
restart: unless-stopped
command: -config.file=/etc/loki/loki-local-config.yaml
networks:
- loki

promtail:
image: grafana/promtail:2.4.0
volumes:
- /var/log:/var/log
- /home/aagern/promtail:/etc/promtail
restart: unless-stopped
command: -config.file=/etc/loki/promtail-local-config.yaml
networks:
- loki

grafana:
image: grafana/grafana:latest
user: "1000"
volumes:
- /home/aagern/grafana:/var/lib/grafana
ports:
- "3000:3000"
restart: unless-stopped
networks:
- loki
Note

The config files MUST be in the inner container directory!

Loki Configs

wget https://raw.githubusercontent.com/grafana/loki/master/cmd/loki/loki-local-config.yaml
 
wget https://raw.githubusercontent.com/grafana/loki/main/clients/cmd/promtail/promtail-local-config.yaml

Promtail config

server:
http_listen_port: 9080
grpc_listen_port: 0
 
positions:
filename: /tmp/positions.yaml
 
clients:
- url: http://loki:3101/loki/api/v1/push
 
# Local machine logs
scrape_configs:
- job_name: local
static_configs:
- targets:
- localhost
labels:
job: varlogs
__path__: /var/log/*log
 
#scrape_configs:
#- job_name: docker
# pipeline_stages:
# - docker: {}
# static_configs:
# - labels:
# job: docker
# path: /var/lib/docker/containers/*/*-json.log

Create the containers

cd ~
sudo docker-compose up -d --force-recreate
sudo docker ps

Data Sources

Go to Grafana :3000 First login/password = admin/admin

Add New Data Source… → Loki
http://loki:3100

Explore {job=varlogs} |= “Network”

Docker logs

scrape_configs:
- job_name: docker
 pipeline_stages:
  - docker: {}
  static_configs:
   - labels:
   job: docker
   path: /var/lib/docker/containers/*/*-json.log

Install a Docker Driver https://grafana.com/docs/loki/latest/clients/docker-driver/

docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions
sudo docker plugin ls

Check config: https://grafana.com/docs/loki/latest/clients/docker-driver/configuration/

{
"debug" : true,
"log-driver": "loki",
"log-opts": {
"loki-url": "http://localhost:3100/loki/api/v1/push",
"loki-batch-size": "400"
}
}
  • Config: vim /etc/docker/daemon.json
  • Restart Docker: sudo systemctl restart docker
Chapter 3

Digital Art

Articles on using different illustration software.

Subsections of Digital Art

Subsections of DaVinci Resolve

Configuration

Global View Settings

Save Monitor Space

  • Menu Workspace → Turn OFF Show Page Navigation

Dual Monitor Config

Note

Requires DaVinci Studio license.

  • Use second monitor for interface: menu Workspace → Dual Screen = ON;
  • Use second monitor for full-screen view of video: menu Workspace → Video Clean Feed=.

Timeline Config

Turn ON Timeline -> Selection follows playhead

Database and files

Create a new database from scratch. Choose PostgreSQL or disk files.

It is recommended to create a separate database + folders for video, pictures, cache etc. for each global project, like Youtube-channel.

Folder structure on disk:

-PROJECT
–Database
–Cache
–SourceMedia (source video)
–Gallery (color correction templates)
–Output (master videos)
–Backups

Project Settings

  • Timeline resolution - the resolution in which the project will be edited. This is NOT the resolution, in which the project will be rendered. So you can edit the whole project in FullHD or 2048x1080DCI faster mode and then render it in 4K UHD or 4096x2160 DCI - DaVinci Resolve will mathematically scale all masks & effects to match the higher resolution on final render. Blackmagic does warn that such scale may cause some approximation;
  • Timeline resolution - may be changed in process of work. Convenient to choose before work;
  • Timeline frame rate - cannot be changed in process of work. Need to choose wisely! For example, transitions correction layers may be only made for specific frame rate (29.97 FPS);
  • Playback frame rate the same as timeline frame rate, but is the playback rate of the connected reference monitor;
  • Video Monitoring section contains parameters for the connected reference monitor;
  • Optimized media resolution - may be changed later, but better to change right away to save performance and space on disk = Half/Quarter from original;
  • Optimized Media Format / Render Cache Format - fastest codec on Win = DNxHR LB, on macos = ProRes LT/Proxy;
  • Enable all checkboxes in this section to save performance the most;
  • Cache files location - insert folder Cache made earlier;
  • Gallery stills location - insert folder Gallery made earlier;

Save current settings as a preset: go to Presets tab in Project Settings, choose Save As.. After saving the preset, choose it with a right mouse click and choose Save As User Default Config.

Image Scaling

Mismatched resolution files = Scale full frame with crop, this setting greatly helps in conform process, if material was re-scaled before color correction.

Color Management

Broadcast Safe: turn ON to help monitor colors to be safe for TV broadcast.

  • -20 - 120 - most lenient option
  • 0 - 100 - most strict option 

Check current colors in Color room go in menu View → Display Safe Broadcast Exceptions

Get the colors back to safe with either using HUE vs SAT instrument, or Gamut Mapping OpenFX plugin (Gamut mapping method = Saturation Compression, lower Saturation Max level). 

Global Preferences

These preferences work for the whole system and all projects.

System - Memory & GPU

  • Resolve Memory usage - give all available memory to DaVinci, if no games played or apps used same time as DaVinci;
  • Fusion Memory Cache - needed for Fusion compositing rendering and playback in real time;
  • GPU - leave Auto in case no problems. For macOS choose = Metal, for PC Intel/AMD card = OpenCL, for NVIDIA choose CUDA. GPU Selection Mode needed for several GPU cards on one computer: one can be used for interface, the other for rendering etc. Are 2 better than 1, should you connect both? Actually this depends on type of cards - does not give significant speed in notebook setup with external videocard, if both the external card and the inner card of the notebook are connected: this gives a few bonus seconds faster render but makes the notebook unusable during render process. Also inner notebook card gets connected to interface and previews, which makes them work slower.

System - Media Storage

  • Create shortcuts for all folders actively used in the project;

System - Decode Options

Needed for work with RAW. Enable all checkboxes - for the GPU to use hardware Debayer on RAW material.

System - Audio Plugins

The folder containing 3rd party VST plugins for Fairlight, in case DaVinci did not find them itself.

User - Project Save and Load

  • Live Save checkbox. This makes DaVinci save any change, like FCPX;
  • Configure Project backups: every 6 min, every 1 hour, every 1 day. 
  • Choose Backup location folder, created previously.

Annotations to video

Create anntations to video to future self or other artists:

  • Use M,M to label the created annotation markers;
  • Use ALT+click on the marker to change its’ duration.

Annotations translate to every other room in DaVinci Resolve.

Proxy Media

Use Proxy to optimize performance. Use column header Proxy to check if proxies were created for video.

In Color Room, turn off proxies to check color drift.

Exposure Guide in DVR

Use Ansel Adams Zone Model to divide exposure in 10 parts

Check the video by using Effects -> False Colors. Configure the effect: Plugin Mode = Creative, Creative Style = Exposure Guide

DVR Hotkeys

Related file

Global Keys

CTRL+Z - Undo

CTRL+SHIFT+Z - Redo

Rooms, Sections

  • SHIFT+1 - Project Manager
  • SHIFT+2 - Media
  • SHIFT+3 - Cut
  • SHIFT+4 - Edit
  • SHIFT+5 - Fusion
  • SHIFT+6 - Color
  • SHIFT+7 - Fairlight
  • SHIFT+8 - Deliver
  • SHIFT+9 - Project Settings

EDIT TAB - Show/Hide Media Pool

EDIT SHIFT+TAB - Show/Hide Inspector

~ - Enter DaVinci Full Screen

ctrl+~ - See video from Preview in Full Screen (to switch betwwen clips use this from Color tab and ARROWUP/ARROWDOWN)

Playhead keys

  • 1 - backwards
  • 2 - pause/run
  • 3 - forwards
  • 4 - In Point
  • 5 - Out Point
  • 6 - Insert selected to timeline at playhead position

In/Out Points

EDIT X - create In/Out points around selected clip

EDIT ALT+X - delete In/Out points

EDIT SHIFT+A - create In/Out points around several selected clips

EDIT Q - Trim to playhead from left

Markers

EDIT M - Create marker (double-tap M,M - create maker & launch marker config window)

EDIT ALT+M - Delete selected marker

EDIT SHIFT+ARROWUP/ARROWDOWN - move playhead between markers

Editing Keys

EDIT W - Split on playhead

EDIT E - Trim to playhead from right

EDIT S - Ripple delete selected clip

EDIT D - Enable/disable clip

EDIT T - Trim edit mode tool

EDIT CTRL+D - Delete all gaps between selected clips

EDIT - ARROWUP/ARROWDOWN - Select prev/next clip in timeline

EDIT - ARROWLEFT/ARROWRIGHT - Move by 1 frame left/right

EDIT - SHIFT+ARROWLEFT/ARROWRIGHT - Move by 1 second left/right

EDIT - . / , - Move selected clip left/right by 1 frame

EDIT - - / = - Zoom Out/In timeline (Also ALT+SCROLLBUTTON or ALT+SCROLLTRACKPAD gesture), also SHIFT+W for Zoom In

EDIT CTRL+SCROLLTRACKPAD - scroll timeline (or CTRL+SCROLLBUTTON)

EDIT SHIFT+SCROLLTRACKPAD - scale in/out track on timeline (or SHIFT+SCROLLBUTTON)

EDIT - SHIFT+Z - Zoom to fit timeline

EDIT - ALT+ARROWUP/ARROWDOWN - bring selected clip up/down a track on timeline

EDIT - SHIFT+~ - Reveal Transform border over selected clip in viewer window

EDIT - CTRL+SHIFT+V (done after CTRL+C) - Paste selected attributes on selected clip / paste clip with no overwrite - moves adjacent clips to the right

EDIT - CTRL+SHIFT+X - Remove attributes from selected clip

EDIT - P - Open clip attributes windows

EDIT - F - turn snapping ON/OFF (also use N)

EDIT - ALT+F - find selected clip from timeline in Media Pool

EDIT - CTRL+SHIFT+F - find & show selected clip from Media Pool in Finder

Compound Clips

EDIT - CTRL+E - create a Compound clip from selected clips

EDIT - CTRL+SHIFT+E - enter inside Compound clip

EDIT - ALT+SHIFT+E - decompose a Compound clip

Clip Timeline Properties / Retime

EDIT - G - turn linked selection ON/OFF

EDIT - C - unlink/link video+audio of selected clip

EDIT - R - Open clip speed window (+Ripple timeline in config will move adjacent clips without overwirting them)

EDIT - SHIFT+R - clip speed overlay on selected clip

EDIT - CTRL+R** - clip speed menu for clip

EDIT - CTRL+SHIFT+R - show retime curve: ALT+click on curve to add speed points fast

EDIT - CTRL+ALT+R - reset retime controls to 100%

EDIT - SHIFT+C - Curve Editor on selected clip

EDIT - CTRL+SHIFT+C - Keyframe Editor on selected clip

EDIT - L - Loop mode ON/OFF

EDIT - SHIFT+SPACEBAR - play the loop (use In/Out points to define the loop interval)

Y - select clip & press to select all other clips to the right in the track from the current selected one CTRL+Y - clip & press to select all other clips to the left in the track from the current selected one

EDIT H - show current Timeline you are working in at Media Pool

Room - Fusion

Articles in section

Fusion Settings

Menu Fusion → Turn OFF Show Toolbar;

Menu Fusion → Fusion Settings…

  • General tab: ProxyStandard change to 4:1;
  • Flow tab: Source Tile Pictures=ON will show video thumbnails in node graph instead of plain rectangles;
  • Flow tab: Arrange to Connected=ON helps organize connected nodes;
  • Flow tab: Show grid=OFF lessens visual garbage on Node graph;
  • Flow tab: Build direction=Vertical to streamline node creation with software such as The Foundry Nuke or SideFX Houdini;
  • Frame format tab: Lock depths=16bit float per channel(64 bit) will significantly enhance inner image processing quality;
  • User interface tab: Zoom Cmd=OFF, Pan Cmd=ON - this will enable zoom with simple mouse wheel like in many other editors.
Note

Reboot of DaVinci Resolve needed after settings saved.

Hotkeys in Node Graph

SHIFT+SPACE - search&create node by name menu

CTRL+T - switch inputs (in merge node switch background & foreground)

Connecting output of one node to the output of another node will automatically create a Merge node;

Tips

Editing Color Curves color correction and other nodes splines in Spline Editor

  • Put a Color Curves node;
  • Open Spline Editor. In its’ menu , choose Show only Selected Tool, then click Zoom to Fit button (diagonal arrows in a square).

Copy perspective view to camera view

  • Connect camera node to Merge node;
  • While Merge node is selected in graph, in perspective windows rotate the view, the Mouse Right-Click → Camera → Copy PoV To → Choose the connected camera.

Reactor Plugin Manager

Link: https://www.steakunderwater.com/wesuckless/viewtopic.php?f=32&t=3067

  • Drag & Drop the Reactor LUA script into Fusion nodes field, install;
  • Check out Reactor in Workspace → Scripts → Reactor → Open Reactor…

To remove plugins from Reactor, use Remove button. Do not un-tick them.

Handy plugins

  • xGlow - advanced version of Glow node;
  • FastExpoGlow - gives a golden light;
  • HeatDistortion - hot air effect.

Subsections of Room - Fusion

Masks

Diagonal Crop

Use Polygon mask in Fusion to create a diagonal crop, with underlying video creatively seen.

Travel Maps

External Link: https://www.youtube.com/watch?v=Vb42fYP6FBU

  • Choose a large map picture, throw it in DaVinci Resolve Timeline;
  • Make the still image video 10 seconds in length;
  • Open the map image video in Fusion Room;
  • Add a Background node and Merge it with the MediaIn node;
  • Add a Polygon node and connect it to Background node;

  • Choose the Polygon node, then in the Viewer draw line segments from point A to B;

  • In Polygon node → Controls → Border Width = 0.006 add border width to see the line. Choose Polygon → Controls → Border Style to make line ends round or square;

  • Change Background node Background → Color to make the line colored appropriately;

  • Animate the Polygon node → Controls → Length from 0 and frame 0 to 1.0 at last frame;

  • In Spline Editor choose both keyframes, make them smooth (hotkey S). Then use hotkey T to select Ease In = 45 and Ease Out = 35;

  • Create map points: add a new Background node, Merge it with the previous Merge node;

  • Add an Ellipse node, drag the border of the ellipse to make it a small point;

  • Animate Ellipse → Controls → Border Width from small negative number to 0 to make the point pop out when travel line starts from it;

  • Go to Spline Editor, choose the Ellipse keyframes and press S to make them smooth;

  • Ctrl+C copy the Ellipse, then Ctrl+V paste it to make other points for the travel line. Move them to appropriate positions;

  • Use the Keyframes editor to move the keyframes of ellipse copies in the appropriate frames of time.

Make Pseudo-3D Effects

  • Add Drop Shadow node, connect it after the Polygon + Background nodes, before the Merge node;
  • Reduce Drop Shadow → Controls → Blur parameter, reduce the Drop Shadow → Controls → Drop Distance parameter to make the shadow closer;
  • Add the DVE node just before MediaOut node;
  • Adjust DVE → Controls → Z Move = 0.3. Adjust DVE → Controls → Rotation → X and Y to make cool 3D-like effect;
  • Make the Viewer into Dual-View, and put result of DVE in one viewer and Merge node before it in second Viewer windows;
  • Adjust DVE → Controls → Pivot → X slightly to see the large X in the viewers. Grab the X and move it to the first point of the line;
  • Animate the Pivot by dragging it to points following the travel line. The use the Spline editor to choose all keyframes and make the animation smooth (S);
  • Go to Polygon node and edit Polygon → Settings → Motion Blur = ON, Quality = 9.

Room - Deliver

General Web Export

Preset = H.264

Quality restrict to:

  • 10000 - 40000 Kb/s - for 1080p
  • 20000 - 80000 Kb/s - for 4K UHD/DCI
  • 6000 - 9000 Kb/s - for Instagram, 720p. More is not needed since video will be re-encoded by their codec.

For RAW footage:

  • Force sizing to highest quality
  • Force debayer to highest quality

Room - Color

Articles in section

Color Room Hotkeys

General

  • SHIFT+D - Grade Bypass (Disable). Disable all nodes to see the original image;
  • CTRL+D - Grade Bypass (Disable) on current node;
  • SHIFT+H - see node mask, or qualifier mask etc. in monitor windows;
  • CTRL+F - See preview window in Full screen.

Create nodes

  • ALT+S - New Corrector (Serial) AFTER selected node;
  • SHIFT+S - New Corrector (Serial) BEFORE selected node;
  • ALT+L - Add Layer lower than current node;
  • ALT+O - New Outside node (Includes everything NOT selected in previous node)
  • ALT+P - New Parallel node.

Tracker

  • CTRL+T - Track mask forward;
  • ALT+T - Track mask backward.

Versions

  • CTRL+Y - add new color grade version;
  • CTRL+N - switch to next version;
  • CTRL+B - switch to previous version.

PowerGrades

Create a PowerGrade folder to store color grading across all projects.

Captured stills can be put into this folder → go into the DaVinci Database and are shared across all projects. A convenient way to store ready-made Color Space Transforms for different camera colorspace/gamma types. Use Append Node Graph to apply the grade, do NOT use Apply Grade since this changes the original ISO of the footage,  which may differ in the specific scene.

Tracker

Cloud tracker - tracks using a whole mask with all its’ points.

Point tracker - needs control points on contrast areas, which it uses to track.

Stills

Right-click on footage → Grab Still: this saves the footage with currently applied grade. After any changes to the image, you can find the still in Gallery, and:

  • double-click on still to make a A|B compare picture. CTRL+W to see changes on full screen (disable still);
  • right-click → Apply Grade to apply to what was saved. Stills can be applied to any footage to copy correction or try interesting results.

Subsections of Room - Color

Color Balance

Tip to help reach ideal neutral color balance.

  • Create Serial Node for color balance correction  and 2 Layer nodes;
  • Apply OpenFX module Color Generator to second Layer Node;
  • In Color Generator options choose Color = HEX #808080 (neutral gray);
  • Choose Composite Mode = Luminosity;
  • In balance Serial Node, add +Saturation to see color offset;
  • Turn on Scopes = Parade;
  • Use Offset Wheel to neutralize colors (Parade RGB levels equal);
  • Turn OFF the layer node with color generator.

Color Correction

Blown Highlights

They can be restored using several methods:

  • Use the wheels: lower Gain, then create node and restore contrast;
  • Use the Curves: lower the curve in the highlight area, make “S” form to restore contrast;
  • In the Curves instrument, use Soft Clip section: Low / High restores shadows / highlighs, while Low Soft / High Soft makes a smooth transition - ideal for blown sky areas.

Parasite color in highlights

Typically such things appear when shooting on 8bit cameras. Use Lum Vs Sat Curve to fix.

  1. Isolate highlights by luminosity;
  2. Bring saturation level in highlights down to remove color.

Color Grading

Scenarios

Свет в городском пейзаже

  • Закат - много красного, оранжевого в средних и в светлых тонах, синий цвет в тенях противопоставляется;
  • Рассвет - более холодный чем закат, больше зелёного чем красного/оранжевого;
  • Ночь - в кино обычно синяя ночь, чем чёрная. Небо сохраняет светлость;
  • Унылый серый город - снизить насыщенность (Saturation), чтобы это подчеркнуть.

Цвет в портрете

Хакрактер рисуется контрастом. Обычно грейдинг начинается с опусканием вниз теней. Дальше отделение от фона.

Работа с ЧБ портретом

Человек не воспринимает ЧБ как именно ЧБ, он ищет и выдумывает немного цвета. Поэтому ЧБ не делают полностью лишённым цвета, его чуть-чуть подкрашивают для придания настроения: для тёплого настроения подкрашивают в коричневый, для “глянцево-журнального” настроения - в синий. Количество подкраса совсем маленькое, совсем чуть-чуть, как петрушка в супе.

Teal & Orange

  • Start by grading the image into nice blue using all color wheels (Lift, Gamma, Gain), and distribute the wheels in a clockwise fashion between each other!

  • All colors in the image turn blue, but shadows have to stay neutral: user the Log wheels to fine tune: Shadow, Midtone, Highlight wheels between Low Range (default lower than 33% = shadows) and High Range (default higher than 66% = highlights). Turn the Shadow wheel to blue counterpart to make shadows neutral. Lower the Low Range (around 10%) so that midtones are not affected;

  • THEN mask out skin and other “warm elements” in the picture (in parallel node) and grade them into the opposite color - yellow-orange;

  • Use the Log wheels to balance the colors: make a soft bridge between warm skin and cold background by turning Shadow wheel to green a bit, Highlight to magenta;

  • Balance the footage colors, so the color work together, and not appear as isolated elements: one cheater method to do this is to radically darken the background. Since with teal&orange we get the maximum color contrast, it is better to make the light contrast stronger as well. Another method is to make subtle changes - bring the overcolored ares back by lowering saturation, for example.

Color Wheels → Color Boost (number in the below section) - increases/decreases the saturation of the MOST SATURATED colors in the image. Helps if you have some over-saturated colors which you want to level down, without touching the rest if the image.

Add Saturation to high values, then subtract Color Boost to remove color spills.
Example: vegetables in a white glass bowl will pass their color to the bowl. To keep it white, subtract Color Boost.

​​Цветокоррекция и покрас плёночного кино

В плёночном кино грейдинг являлся этапом PRE-Production. Это связано с необходимостью подбора правильной плёнки, дающей нужную гамму цветов. Для проявки плёнки проявочные машины (пример - российская машина МПМ—16—3М), которые работали по заданному СТАНДАРТУ.

Color Management

Videocard sends a signal to the monitor, divided in 2 components: COLORSPACE (color) and GAMMA (light)

COLORSPACE

Initial colorspace is a triangle-like model of all colors which are discriminated by the human eye (mathematical model): Different color spaces encompass different sets of colors as compared to this model: rec709 - came with digital TV, P3 - used for digital cinema theater, rec2020 - UHDTV / new 4K video standard.

GAMMA (Dynamic Range)

Digital cameras see the world in a linear fashion. If we have a room with 1 lamp and we bring in a second lamp, camera will register 2x increase of light. Human eye will register around +20% increase of light. Transfer function - math function to compress the light data of an image and then use this file to restore the whole dynamic range seen by the human eye.

  • Human eye dynamic range: 10-13 stops active range, 24 stops total range
  • Film dynamic range: 13 stops
  • RED Weapon Camera: 16.5+ (incl HDRX-mode) stops
  • HLG: 12 stops
  • REC709: 6 stops

REC709 GAMMA 2.4 is the native format for DaVinci Resolve, Adobe Premiere etc. If you throw a different colorspace file in DaVinci, you must tell it what it is and convert: Color room → Library → ResolveFX Color → Color Space Transform

Fir SONY SLog3 shoot with +2 or +3 F-stops higher to quell noise, then choose:

  • Input Colorspace = Sony S-Gamut3
  • Input Gamma = Sony S-Log3
  • Output Colorspace = Rec709
  • Output Gamma = Rec709
  • Tonemapping = Luminance Mapping

Color Masks

Using Masks for Correction

Always use the curved mask: it is the only one that has soft transitions, which can be changed individually for different sides:

Neural Masks

Davinci Resolve Studio 18+ brings Object Mask to select parts of a person. Draw lines with dropper+ over face & other parts of a person to highlight them. Use dropper- to leave other objects out of the mask. Use Better quality with Refine Edge BEFORE calculating the mask.

You can see the mask using Corrector node.

Use Key Mixer node to combine several masks (you can input more than 2, links are added in Key Mixer node automatically upon connection), invert them using button in Key Mixer config.

De-Noiser

Noise

Most of the noise is in red channel. Use DaVinci Resolve Studion version with 2 types of de-noise.

Motion Effects → Temporal NR

  • In case of light noise: Frames=3, Mo. Est. Type=Faster, Luma=Chroma=~10+
  • In case of heavy noise: Frames=5, Mo. Est. Type=Better, Luma=Chroma=~20+

Motion Effects → Spatial NR

Unlock Chroma != Luma. Raise Chroma=6-7

Moire

Moire on buildings and other landscape objects is countered by motion blur.

Motion Effects → Motion Blur

Mo. Est. Type: Better, Motion Range=Small, Motion Blur=100 (maximum).

Node Structures

Color Correction Structure

Color Correction structure contents:

  • global denoise to reach pristine look for selection
  • node for primaries
  • node for curves
  • basic secondaries + additional secondaries nodes
  • vignette node, then Add Node → Outside Node, then finally the global look

Color Grading Structure

Color Grading structure contents:

  • 3 global corrector nodes: color balance, global exposition, misc global node (noise reduction?)
  • 3+ local exposition corrector nodes: use masks for areas and add light, like lighting up the face
  • Parallel mixer with 3 inputs
  • 3+ local color corrector nodes: use masks for areas and change saturation, like de-saturating items with bright colors, which may drag unwanted attention
  • Parallel mixer with 3 inputs
  • 1 corrector node with a combination of LUTs to produce a special look (color grading for drama / atmosphere / mood goes here)

Parallel Nodes

Changes in parallel nodes are made to the image with the same intersection, as if the nodes were in sequence (Serial type). The difference is that all parallel nodes read data from one initial node, while in serial sequence the next node reads data from previous node with all corrections applied on the previous step.

Node Layers

Layers are parallel nodes, but work in opposite direction: lowest layer has the highest precedence. Layers can be composited using connecting node settings.

Node Sequence

Nodes sequence matters

  • Create 2 nodes: in the first darken image using a LIFT wheel. In the second node make it red with the LIFT wheel and raise Saturation to 100%;
  • Make a second version with nodes in reverse sequence;
  • See the difference: if darken is first, then more areas become red using the second node than vice-versa.
Tip

Instruments sequence also matters: in the Color Wheels section, Lift/Gamma/Gain wheels are applied BEFORE the general Offset wheel.

Contrast sequence matters

When you use a node to make stronger contrast on a footage, you basically stretch the image info between Dark & Light boundaries. If you create a second node after the “contrast creation” one, you will be changing the resulting stretched image data, and the changes will be more subtle and soft.

Skin and Sky

Skin Exposure

For dark skin (african-american) use contrast+pivot to the left to lighten up, while maintaining contrast:

For pale skin (caucasian) use contrast+pivot to the right to darken, while maintaining contrast:

Tip

If you lift the lower midtones, you need to make up for Saturation loss - raise it.

Glow

Use Glow OpenFX plugin on skin selection to add a bit of skin light (Shine Threshold = ~0.6)

(Quick) Skin Retouch

Skin tones are “Midtones”. So use Qualifier and masks to select the skin, and subtract the eyes & brows & mouth.

  • Raise Color Wheels → (page 2) Midtone Detail (MD) for boys to underline harshness and manliness;
  • Lower Color Wheels → (page 2) Midtone Detail (MD) for girls to smooth out their faces (around -80);
  • Lower overall effect of filter to make it look more natural Key → Key Output → Gain = 0.8 - 1.0

Sky Selection

Use Luminance qualifier for the cleanest sky selection.

Vignette

  • Хорошо работают при неоднородном фоне. На однородном они слишком очевидны
  • Делают видео более объёмным, отделяют передний и средний планы от заднего

Способ создания 1

  • OpenFX → ResolveFX Stylize → Vignette

Способ создания 2

  • Обычно виньетки применяются на уровне Timeline
  • Добавить новую ноду (ALT+S), добавить к ней круговую маску
  • Добавить outside node (ALT+O), с помощью колец Color Wheels опустить Gain и Saturation, в разделе Key снизить Key Output → Gain до 0.8-0.9, чтобы смешать с оригинальной картинкой Способ 2 лучше чем 1, т.к при способе 1 по углам создаются чёрные области с растушёвкой, в то время как при способе 2 происходит “multiply” видеопотока на себя с коррекцией = такой способ даёт менее “грязный” результат.

Способ создания 3

  • Создать ноду Vignette;
  • Создать круговую маску на субъекте, с высокой мягкостью (Soft = 25);
  • Зайти в Curves, основную кривую опустить примерно по центру вниз;
  • Добавить outside node (ALT+O),
  • В этой ноде зайти в Curves, основную кривую немного поднять (либо создать обратную S-кривую, поднять лишь тени), чтобы высветлить субъекта.
Chapter 4

Getting Things Done

Everything around documenting, note taking, mind maps, schemes & diagrams.

  • Mermaid

    Drawing diagrams with Markdown

Subsections of Getting Things Done

Mermaid

Tutorials: https://mermaid.js.org/config/Tutorials.html

Online editor + exporter: https://mermaid.live

Installation

Mermaid has plugins for VS Code, Obsidian, works with GitHub, GitLab etc.

Full list of integrations: https://github.com/mermaid-js/mermaid/blob/develop/docs/misc/integrations.md

Flowcharts

Example:

graph LR  %% TD = Top->Down, LR = Left->Right etc.
S[Start] --> A;
A(Enter your EMail) --> E{Existing user?};
E -->|No| Create(Enter name)
E -->|Yes| Send(Send a letter to address)
Create --> EULA{Accept conditions}
EULA -->|Yes| Send
EULA -->|No|A

Result:

graph LR S[Start] --> A; A(Enter your EMail) --> E{Existing user?}; E -->|No| Create(Enter name) E -->|Yes| Send(Send a letter to address) Create --> EULA{Accept conditions} EULA -->|Yes| Send EULA -->|No|A

Sequence Diagrams

Example:

sequenceDiagram
autonumber               %% action numbers placed on every arrow
actor C as Client
Note left of C: User     %% [ right of | left of | over ] supported
participant I as Identity Provider
participant S as Service Provider
Note right of S: Blitz Identity
C->>S: Resource request
activate C
activate S
S-->>C: Redirect to Identity Provider
deactivate S
loop every hour           %% loop start
C->>I: Request Access Token
activate C
activate I
I-->>C: Access Token
deactivate C
deactivate I
end                       %% loop end
C->>S: Access granted
Note over C,S: Browser access
deactivate C

Result:

sequenceDiagram autonumber actor C as Client Note left of C: User participant I as Identity Provider participant S as Service Provider Note right of S: Blitz Identity C->>S: Resource request activate C activate S S-->>C: Redirect to Identity Provider deactivate S loop every hour C->>I: Request Access Token activate C activate I loop I->>I: Kerberos cert end I-->>C: Access Token deactivate C deactivate I end C->>S: Access granted Note over C,S: Browser access deactivate C