пятница, 27 декабря 2019 г.

Владение ресурсами в Rust. Часть 4.

Продолжим рассматривать передачу владения по ссылке. Еще раз напомню, что физической передачи владения при этом не происходит, ни копирования, ни перемещения. В Rust это называется “одолжить” (borrowing). В обычном представлении одолжить значит сначала дать (lend) или взять (borrow) какую-то вещь, а потом ее вернуть и это неправильно. Более подходящее слово “разделить” (sharing). Например, когда у вас есть источник энергии и вы даете возможность подключиться к нему кому-то еще. Причем если вам захотелось его отключить, вам никто не может помешать это сделать, и энергия пропадет у того, кто к вам подключен в это время. Это известная проблема висячих ссылок. Чтобы этого избежать в Rust есть специальный инструмент borrow checker, который отвечает за проверку времени жизни ссылок, т. е. чтобы никто не отключил энергию в момент использования потребителями.

Если контролер ссылок не может понять время жизни каких-то ссылок, то выдаст ошибку на этапе компиляции, с просьбой указать это время явно. Тут нужно сказать, что проверка происходит всегда, но в простых ситуациях, контролер предполагает некоторое время жизни по умолчанию, и не требует указывать его. Например, при передаче одной ссылки в качестве параметра функции и одной в качестве возвращаемого значения, контролер предполагает, что время жизни принимаемой ссылки равно времени жизни возвращаемой. Если на вход передать две ссылки, то контролер потребует определить их время жизни. Время жизни указывается с помощью знака “ ’ ” с буквой. Сама по себе буква не является переменной, а служит именем канала для проверки. Разные ссылки могут быть “связаны” через один общий (‘a) канал или через несколько разных (‘a, ’b).

Понимание как это работает пришло ко мне, когда я представил связи в виде системы трубопроводов. Источником будут служить наши данные (переменные x и y), на которые мы хотим ссылаться. Потребителем будет переменная z, которая примет возвращаемое значение из функции. Указывая одинаковое время жизни для входных параметров и возвращаемого значения, мы определяем условие работы функции, при котором потребитель получит результат работы функции при условии, что источник в это время доступен.


 
Трубопровод проходит от источника к потребителю через тело функции, в которые передается и возвращается поток (значения, которые мы передаем по ссылке). Перебои водоснабжения — это ошибки, которые мы хотим избежать. Контролер ссылок прослеживает путь, и сообщает об ошибке если связь прервана, либо просит указать по какому каналу они будут связаны, если их несколько. Что может быть причиной разрыва канала? Например, перемещение владения ресурсами, на которые мы ссылаемся, после чего наши значения становятся не доступными по ссылке. Это может произойти, если внутри функции (или за ее пределами) мы передадим наши данные в другую функцию или попробуем вернуть локальную ссылку, созданную внутри функции, без передачи ей наших данных. Смотрим пример.

//
#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

#[derive(Debug)]
struct Line<'a> {
    start: &'a Point,
    end: &'a Point,
}

//
//default lifetime
fn positive(value1: &i32) -> &i32 {
    if *value1 > 0 {
        value1
    } else {
        &0
    }
}

//borrowing parameters by refs - it is SHARING! (no move, no copy)
fn smaller<'a>(value1: &'a i32, value2: &'a i32) -> &'a i32 {
    if *value1 < *value2 {
        value1
    } else {
        value2
    }
}

fn main() {
    //default lifetime
    let n = -5;
    let pos_n = positive(&n);
    println!("{}", pos_n);

    //Sharing with function
    let x = 5;
    let y = 10;

    //lending to fn - it is SHARING! (no move, no copy)
    let z = smaller(&x, &y);
    println!("{}", z);

    //Sharing with struct
    let a = Point { x: 0, y: 0 };
    let b = Point { x: 1, y: 1 };

    //let d = a; //ERROR! move ownership from a to d

    //lending to struct - it is SHARING! (no move, no copy)
    let c = Line { start: &a, end: &b };
    println!("{:?}", c);
}

Перегуд В.

вторник, 24 декабря 2019 г.

Обработка исключений в Rust

В Rust нет обработки исключений, такой как в си++, в виде блоков try и catch. Вместо этого..

Некоторые личности считают, что одной из величайших ошибок при дизайне некоторых языков программирования, было – придумать пустой указатель (0, NULL, nullptr). С одной стороны, это удобно, с другой стороны это очень неудобно по причине необходимости постоянно проверять, а не является ли указатель пустым. И так как в этих языках спохватились поздно, то проверка полностью ложится на программиста или можно использовать некоторый синтаксический сахар, но главное в том, что на уровне самого языка эти проверки не производятся или заставляют программиста выполнять лишние действия. Однако есть исключения, например, Rust. Об этом мы сегодня и поговорим.

Благодаря возможности хранить значения для вариантов в перечислениях, а перечисления как мы знаем это тип данных, который хранит значение одного из своих вариантов, мы можем создать перечисление с двумя вариантами – правильно и неправильно. К каждому из этих вариантов мы можем прибить значение определенного типа. Для правильного варианта пусть это будет тип нужного нам значения (к примеру i32, [T,n], Vec<T>, String, и т. д.), который мы можем задавать, например, через генерик переменную. Для неправильного варианта, в принципе тоже самое, но по умолчанию пусть будет строка с описанием, что пошло не так. Такое перечисление можно использовать самостоятельно как тип возвращаемого значения, который либо имеет правильный вариант с нужным нам значением, либо имеет неправильный с описанием ошибки или пустой. Такое перечисление мы можем инициализировать при возврате из функции и проверять на правильность при получении. Инструмент есть, но все нужно обрабатывать самому. В чем тогда разница с описанными выше языками?

#[derive(Debug)]
pub enum Res<T, E> {
    Thing(T),
    Error(E),
}

impl<T, E> Res<T, E> {
    pub fn unwr(self) -> T {
        // multi cases
        // match self {
        //     Res::Thing(i) => i,
        //     _ => panic!()
        // }

        // one case
        if let Res::Thing(i) = self {
            i
        } else {
            panic!()
        }
    }
}

pub fn divide(a: i32, b: i32) -> Res<i32, String> {
    if b == 0 {
        return Res::Error("Divide by zero".to_string());
    }
    Res::Thing(a / b)
}

fn main() {
    let a = divide(4, 0);
    let b = divide(4, 2);
    println!("a = {:?}", a);
    println!("b = {:?}", b.unwr());
}

А в том, что в самом Rust и в его стандартной библиотеке этот прием используется повсеместно.  Например, обработка неправильного варианта такого перечисления не приводит к неопределенному поведению, а вызывает аварийное завершение, что позволяет избежать ошибок на ранней стадии. Более того для удобства есть два таких перечисления. Первое это Option, которое имеет вариант Some(T) и вариант None. Второе это Result, у которого есть вариант Ok(T) и вариант Err(T). Option можно использовать например как возврат из функции поиска, которая либо что-то нашла, либо нет и оба этих варианта правильные, т.е. отсутствие искомого не является ошибкой работы функции. Например, метод pop(), который удаляет элемент из вектора, возвращает такое перечисление, которое имеет значение элемента, завернутое в правильный вариант, либо ничего при пустом векторе. В свою очередь Result можно использовать тогда, когда мы ждем успешное завершение, но можем получить ошибку, которую нужно перехватить. Например, функция связи с сервером должна соединиться, но при отсутствии сети, должна сообщить об ошибке.

Самый простой способ обработать Result это использовать оператор “?”. При правильном варианте происходит распаковка значения (которое мы можем присвоить переменной или вызвать метод его типа через "." ), а при ошибке, происходит аварийное завершение приложения. Рассмотрим различные варианты работы с Result в примере:


fn foo_vec(mut v: Vec<i32>) -> Result<Vec<i32>, &'static str> {
    if !v.is_empty() {
        for i in 0..v.len() {
            v[i] += 1;
        }
        println!("\nvec in foo: {:?}", v);
        Ok(v)
    } else {
        println!("\nvec in foo []");
        Err("error! vec is empty")
    }
}

fn main() -> Result<(), &'static str> {

    let v3 = vec![1, 2, 3];
    let v4 = foo_vec(v3);
    println!("vec is not empty, result: {:?}", v4);
    //vec in foo: [2, 3, 4]
    //vec is not empty, result: Ok([2, 3, 4])

    let v6 = vec![];
    let v7 = foo_vec(v6);
    println!("vec is empty, result: {:?}", v7);
    //vec in foo []
    //vec is empty, result: Err("error! vec is empty")

    if v7.is_err() {
        println!("\nvec is empty, result: {:?}", v7.unwrap_err());
    } else {
        println!("\nvec is not empty, result: {:?}", v7.unwrap());
    }
    //vec is empty, result: "error! vec is empty"

    let v8 = vec![5, 5, 5];
    let v9 = foo_vec(v8.clone())?; //?
    println!("? >> vec[0]: {:?}", v9[0]);
    //vec in foo: [6, 6, 6]
    //? >> vec[0]: 6

    let v10 = match foo_vec(v8) { //match
        Ok(v) => v,
        Err(e) => return Err(e),
    };
    println!("match >> vec: {:?}", v10);
    //vec in foo: [6, 6, 6]
    //match >> vec: [6, 6, 6]

    Ok(()) //() - empty tuple, like void in C++
}

Перегуд В.

воскресенье, 22 декабря 2019 г.

Владение ресурсами в Rust. Часть 3.

Двигаемся дальше. Сегодня мы рассмотрим передачу по ссылке (без передачи владения). Про передачу по значению (с передачей владения) мы уже все знаем и останавливаться на этом не будем. У нас есть возможность избежать и копирование, и перемещение с помощью прямой ссылки на область памяти, которая хранит наше значение, т. е. на наш ресурс. Кстати при перемещении происходит нечто подобное по смыслу, мы просто передаем (перемещаем) адрес памяти (с нашим значением) от одной переменной к другой. Исходным условием этого является наличие именованной области памяти, созданной явно (с помощью переменной) или не явно (литерал значения). Таким образом мы можем ссылаться на уже существующую переменную или значение, но наиболее полезной является возможность принимать и возвращать ссылки из функции. Более сложные способы применения, а именно наличие ссылок в виде полей пользовательских типов, мы рассмотрим следующий раз. Тип ссылка определяется с помощью символа &, например: let x:i32 = 5; let r_x:&i32 = &x;. Указывать тип не обязательно, он выводится автоматически: let r_x = &x;

Как мы помним, в си++ ссылки появились как инструмент упрощающий работу с указателями. По сути это синтаксический сахар и внутри мы имеем указатели, а значит ссылки явно или не явно имеют значение типа в своем определении. Ссылки очень полезны в работе, т. к. мы не копируем сами области памяти с данными, а копируем только значения адресов памяти, что значительно меньше нагружает систему и ускоряет работу приложения. Например, при передаче больших данных в функцию и из нее, мы можем избежать дублирования этих данных. В отличие от ссылок в C++, где ссылки по умолчанию позволяют модифицировать данные, в Rust по умолчанию эти данные не доступны для модификации. Чтобы сделать возможным изменение данных потребуется использовать mut в определении ссылки. Например, let r_xx = &mut xx;. Чтобы изменить значение, на которое указывает ссылка, нам нужно разыменовать ссылку с помощью оператора *, например: *r_xx = 10;.

Обращаю ваше внимание на то, что обозначение ссылки (включая возможность модификации) производится явно и для параметра, и для аргумента функции. Это отличает Rust от C++ в лучшую сторону, т.к. при передаче аргумента в си++ (без указания, что мы передаем по ссылке), затрудняется чтение и понимание кода и требуется знания параметров функции, благо современные IDE облегчают эту задачу. По умолчанию ссылка может быть проинициализирована один раз и мы не можем ее изменить до тех пор, пока не укажем еще один mut в определении:  let mut r_xx = &mut xx;. Теперь мы можем присвоить ей новое значение, например: r_xx = &mut yy;. Но есть еще одно отличие от C++ в котором, мы можем иметь несколько модифицирующих ссылок на один объект, что в свою очередь снижает безопасность. В Rust это невозможно, в определенной области для одного ресурса мы можем иметь только одну модифицирующую ссылку ИЛИ несколько не модифицирующих.  Это ограничение предотвращает гонку за данными. При необходимости мы можем разделить ссылки в разных областях действия или блоках.

При передаче по ссылке не происходит передачи владения ресурсом, при окончании времени жизни ссылки не происходит уничтожения значения и освобождения памяти. Область действия ссылки начинается с того места где она была введена и продолжается до того места где она была последний раз использована. Если области действия ссылок не пересекаются, то гонки за данными не происходит. При возврате ссылки на локальную переменную из функции, в отличие от C++ в Rust потребуется указать специальный атрибут времени жизни, что предотвратит создание опасных висячих ссылок, об этом мы поговорим следующий раз. Вместо этого можно вернуть саму локальную переменную, при этом по умолчанию, для пользовательских типов, произойдет перемещение владения без дублирования ресурса.

#![allow(unused_assignments)]

fn main() {
    //&i32 - reference to i32
    let xx = 0;
    let r1_xx = &xx;
    println!("{}", r1_xx);

    //&mut i32 - reference to mutable i32
    let mut yy = 1;
    let r2_yy = &mut yy;
    //* - dereference
    *r2_yy = 2;
    println!("{}", r2_yy);

    //&i32 - mutable reference to i32
    let zz = 3;
    let mut r3_zz = &xx;
    r3_zz = &zz;
    println!("{}", r3_zz);

    let mut v = vec![1, 2, 3];
    //mutable reference as function argument
    foo(&mut v);
    println!("{:?}", v);
 
    let vec = vec![1, 2, 3, 4, 5];
    //reference to element
    for elem in vec.iter() { //elem: &i32 - reference
        print!("{} ", *elem); //* - explicit dereference
    }
}

//mutable reference as function parameter
fn foo(v: &mut Vec<i32>) {
    // i: &mut i32
    for i in v {
        //*i - dereference
        *i += 1;
    }
}

Перегуд В.

пятница, 20 декабря 2019 г.

Владение ресурсами в Rust. Часть 2.

Продолжаем. Экономия памяти, контроль висячих ссылок, перемещение владения по умолчанию, все эти правила повышают скорость работы, безопасность и надежность приложения. Но упрощают ли они работу программиста? И да, и нет. С одной стороны, мы сокращаем количество ошибок, которые нужно будет искать на тестировании и править на этапе поддержки. С другой стороны, это требует более продуманной архитектуры приложения и оригинального подхода в решении задач, т. к. старые проверенные решения не очень подходят. Есть ли возможность сделать переходный период более легким? Да. Точно так же, как в C++ можно соблюдать правила безопасности с помощью std::move() и умных указателей, в Rust есть возможность отойти от них и писать не безопасный код при необходимости.

Сегодня мы рассмотрим управление копированием в Rust. Это нужно не только для упрощения архитектуры и снижения безопасности, но также это нужно для правильной работы копирования в ситуациях, когда это необходимо. Так же как в C++ есть конструктор перемещения, в Rust есть trait (интерфейс) копирования, который позволяет явно или не явно производить копирование ресурсов. Обращаю ваше внимание, что все сказанное далее будет относиться к пользовательским типам. Также смотрите первую часть статьи, предыдущий пост.

Первичным является имплементация интерфейса Clone для явного копирования, это позволяет нам копировать ресурсы например вот так let y = x.clone(); В этом выражении явно указано, что мы копируем, и это облегчает чтение и понимание кода. Без .clone() будет происходить перемещение. Но мы можем пойти еще дальше, и имплементировать интерфейс Copy, который в свою очередь требует наличия Clone. Тогда в let y = x; копирование будет происходить не явно вместо перемещения и .clone() не требуется. Более того в простых ситуациях, мы можем не имплементировать эти интерфейсы самостоятельно, а наследовать их с помощью атрибута #[derive(Clone, Copy)]. И все будет работать как описано выше. Смотрим пример.

point.rs
--------
//!Custom type Point
///attributes
#[derive(Debug)]
//#[derive(Clone)]
//#[derive(Copy)]
///Fields
pub struct Point {
    x: i32,
    y: i32,
}
///Methods
impl Point {
    ///constructor
    pub fn new() -> Self {
        Point { x: 0, y: 0 }
    }

    pub fn add(&self, n: i32) -> Self { //copying i32 to
        Point { x: self.x + n, y: self.y + n } //moving Point from
    }

    pub fn sum(&self, p: Point) -> Self{ //moving Point to
        Point {
            x: self.x + p.x,
            y: self.y + p.y
        } //moving Point from
    }
}
///implement trait Clone
impl Clone for Point{
    fn clone(&self) -> Self {
        Point{
            x: self.x,
            y: self.y
        }
    }
}
///implement trait Copy
impl Copy for Point{ }

main.rs
-------
#![allow(unused_variables, unused_imports, dead_code)]
mod point;
use point::Point;

fn plus(a: i32, b: i32) -> i32 { //copying to
    a + b //copying from
}

fn main() {
    //ownership basic types
    let x: i32 = 5; //assign
    let y: i32 = x; //copying assign
    println!("{}", x); //x after copying

    let z = plus(x, y); //copying to and from fn
    println!("{} + {} = {}", x, y, z); //OK!!!

    //ownership custom types
    let p1 = Point::new(); //construct
    let p2 = p1; //p1 moving assign by default
    println!("{:?}", p1); //OK!!!

    let p3 = p2.add(z); //copy i32 to method, moving Point from method
    println!("{:?} + {} = {:?}", p2, z, p3); //OK!!!

    let p4 = p2.sum(p3); //move p3 to method
    println!("{:?}", p3); //OK!!!

    //attribute #[derive(Clone)] for Point
    //let p4 = p3.sum(p3.clone()); //clone explicit
    //println!("{:?}", p4); //OK!!!

    //attribute #[derive(Copy)] for Point
    //println!("{:?}", p1); //OK!!! after implicit copy in: let p2 = p1;
}

Тип String из стандартной библиотеки Rust, имплементирует только интерфейс Clone, поэтому может быть либо перемещен, либо явно скопирован с помощью .clone()

fn main() {
    let mut my_string = String::from("Hello, world!");

    foo(my_string.clone()); //COPY
    println!("{} ", my_string); //OK!

    foo(my_string); //MOVE
    //println!("{} ", my_string); //ERROR!
}

fn foo(s: String) -> String {
    println!("{} ", s);
    s
}

Если мы хотим клонировать вектор пользовательских типов (Vec<Point>), то должны либо сами имплементировать интерфейс Clone, либо наследовать его с помощью атрибута. Для вектора базовых типов (Vec<i32>) этого делать не нужно.

#[derive(Debug, Clone)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p1 = Point { x: 0, y: 0 };
    let p2 = Point { x: 5, y: 5 };

    let points = vec![p1.clone(), p2];
    foo(points.clone());
    println!("points: {:?} ", points); //OK
    println!("p1: {:?} ", p1); //OK
    //println!("p2: {:?} ", p2); //ERROR
}
fn foo(v: Vec<Point>) {
    println!("points: {:?} ", v);
}

Перегуд В.

четверг, 19 декабря 2019 г.

Владение ресурсами в Rust. Часть 1.

Синтаксически владение (ownership) ресурсом обозначается с помощью переменной и ее значения с учетом типа. Например в выражении: int х = 5;, владельцем становится переменная ”x”, которая имеет целочисленное значение 5, для которого в памяти выделено место определенного размера (соответствующего типа). Так как память ограничена по объему, она и является нашим ресурсом. Учитывая возможный размер пользовательских типов, которые могут содержать в себе большие данные, для которых потребуется значительный объем памяти, перед нами стоит задача в эффективном управлении ресурсами.

Смена владельца ресурса происходит, например при присваивании значения 5 второй переменной через первую переменную: int y = x; В этом случае происходит копирование (дублирование, клонирование) памяти, в которой размещается еще одно значение 5, владельцем которой становится переменная “y”. При этом переменная “x” никуда не девается, она доступна на протяжении своего времени жизни. Также передача владения происходит при передаче параметров в функцию и возвращении значения из нее. Такой подход (копирование) в управлении ресурсами используется по умолчанию в C/C++ для всех типов.

Если мы хотим экономно распоряжаться памятью, то нам может потребоваться перемещение ресурса от одного владельца к другому. Мы можем решить эту задачу с помощью указателей, ссылок или специальных инструментов для перемещения значения между переменными. При таком подходе дублирование занимаемой памяти не происходит, значение остается то же, но так как физически у области памяти может быть только одна переменная (владелец), которая указывает на эту область памяти, то при выполнении: int y = x; происходит очистка переменной “x”, после чего она уже не содержит значение 5 и не может быть использована в дальнейшем. Отслеживание такой ситуации, при котором при анализе кода перед компиляцией происходит ограничение в использовании очищенных переменных, является необходимым условием для стандарта безопасности типов. Если этого не делать, то возможны утечки памяти или ошибки, приводящие к неопределенному поведению, что и происходит в C/C++ при неправильном использовании “сырых” указателей.

В Rust по умолчанию для базовых типов используется копирование ресурсов, а для пользовательских типов используется перемещение ресурсов. Смотрим пример.

point.rs
--------
//!Custom type
#[derive(Debug)]
///Fields
pub struct Point {
    x: i32,
    y: i32,
}
///Methods
impl Point {
    ///constructor
    pub fn new() -> Self {
        Point { x: 0, y: 0 }
    }

    pub fn add(&self, n: i32) -> Self { //copying i32 to
        Point { x: self.x + n, y: self.y + n } //moving Point from
    }

    pub fn sum(&self, p: Point) -> Self{ //moving Point to
        Point {
            x: self.x + p.x,
            y: self.y + p.y
        } //moving Point from
    }
}

main.rs
-------
#![allow(unused_variables, unused_imports, dead_code)]
mod point;
use point::Point;

fn plus(a: i32, b: i32) -> i32 { //copying i32 to
    a + b //copying i32 from
}

fn main() {
    //ownership basic types
    let x: i32 = 5; //assign
    let y: i32 = x; //copying assign
    println!("{}", x); //x after copying

    let z = plus(x, y); //copying to and from fn
    println!("{} + {} = {}", x, y, z); //OK!!!

    //ownership custom types
    let p1 = Point::new(); //construct
    let p2 = p1; //p1 moving assign by default
    //println!("{:?}", p1); //ERROR: value borrowed here after move

    let p3 = p2.add(z); //copy i32 to method, moving Point from method
    println!("{:?} + {} = {:?}", p2, z, p3); //OK!!!

    let p4 = p2.sum(p3); //move p3 to method
    //println!("{:?}", p3); //ERROR: value borrowed here after move
}

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

fn foo_vec(mut v: Vec<i32>) -> Vec<i32> {
    for i in 0..v.len() {
        v[i] += 1;
    }
    println!("vec in foo: {:?}", v);
    v //MOVE
}

fn foo_arr(mut a: [i32; 3]) -> [i32; 3] {
    for i in 0..3 {
        a[i] += 1;
    }
    println!("arr in foo: {:?}", a);
    a //COPY
}

fn main() {

    println!("\n<<< Arrays >>>");
    //Arrays - copy by default
    let a1 = [0; 3];
    foo_arr(a1); //COPY
    println!("arr in main: {:?}", a1);

    println!("\n<<< Vectors >>>");
    //Vectors - move by default
    let v1 = vec![1, 2, 3];
    foo_vec(v1.clone()); //COPY explicit
    println!("vec in main: {:?}", v1);

    let v2 = vec![1, 2, 3];
    foo_vec(v2); //MOVE
    //println!("vec in main: {:?}", v2); //ERROR!

    let v3 = vec![1, 2, 3];
    let v4 = foo_vec(v3); //MOVE
    println!("vec in main: {:?}", v4); //OK!
}

Перегуд В.

пятница, 13 декабря 2019 г.

Бит как единица хранения информации.

Сегодня мы коротко поговорим о том, как хранится информация и что такое бит (bit) в программировании и компьютерах. Представим физический смысл хранения – что-то либо есть, либо его нет, и его количество. В нашем случае это носитель информации, например память компьютера или твердотельный накопитель, в котором мы имеем какое-то количество переключаемых триггеров, которые могут быть вкл. или выкл. В виде ячеек для хранения информации, которые могут хранить ноль или единицу.

Так, отлично, одна ячейка может хранить один бит информации. Чем больше таких ячеек, тем больше мы можем хранить информации. Для удобства мы можем группировать ячейки чтобы хранить информацию в группах, которые можем обработать за один раз, кажется это называется разрядностью. Какую информацию мы можем хранить? Например цифры, буквы, логические понятия, все то, что поможет нам организовать данные в осмысленную информацию. Например, цифр у нас десять, а в русском алфавите 32 буквы, плюс какие-то символы или знаки препинания, т.е. например первая цифра это ноль, а первая буква это “а” и так далее.

Что бы хранить десять цифр (0-9), из которых мы потом можем составлять любые числа, нам нужно на уровне железа хранить каким-то образом 10 состояний. Мы помним, что одна ячейка хранит один бит, т.е. два состояния (0 или 1). Если мы сгруппируем две ячейки, то получим 4 состояния (00 11 01 10). Все еще мало для представления десяти основных цифр, идем дальше. Для удобства будем группировать кратно двум, чтобы сократить количество одновременной обработки групп, которые нужно обрабатывать за один вычислительный проход. Ок, давайте сгруппируем четыре ячейки, получим 2 в 4й степени, т. е. 16 состояний. Для цифр норм, но все еще мало для алфавита. Ок, группируем 8 ячеек, получаем 256 состояний, теперь этого достаточно чтобы описать цифры и основные алфавиты, например английский. Но есть исключения, например иероглифы азиатских языков, для которых нужно больше битов для хранения всех состояний, необходимых для хранения всех символов.

Теперь нужно понять, что чтобы записать любое число на экране нам достаточно десяти состояний (для десяти цифр), но чтобы вычислять математические выражения нам нужен отдельный набор состояний для каждого числа, таким образом, с помощью 8 бит, мы можем получить целое число от 0 до 256 (или от -128 до +127). И этого не очень хватает, значит нам нужно наращивать наши группы дальше. Для удобства, по мере возрастания, назовем наши группы байтами (1byte = 8bit), килобайтами, мегабайтами, гигабайтами и т.д. В свою очередь 2 байта дают нам 65535 состояний, а 4 байта 4294967295 состояний, т.е. такие числа мы можем использовать для расчётов без учета знака, или в два раза меньше, если мы условимся, что половина из них может быть отрицательным значением, а вторая положительным. В программировании это называется типами, которые нам нужно указывать для переменных, содержащих какую-то порцию информации для дальнейшего использования.

Bitwise operators 

Перегуд В.

четверг, 12 декабря 2019 г.

Basic types in Rust

use std::mem;

// i8 u8 i16 u16 i32 u32 i64 u64
// isize usize - platform specific (32bit/64bit)
// char
// f32 f64
// true false

fn main() {
    //immutable by default
    let a: u8 = 255;
    //unsigned integer 8bits = 1byte - from 0 to +255 (256 states)
    println!("a = {}, size: {} bytes", a, mem::size_of_val(&a));

    //mut - mutable
    let mut b: i8 = 127;
    //signed integer 8bits = 1byte - from -128 to +127 (256 states)
    println!("b = {}, size: {} bytes", b, mem::size_of_val(&b));
    b = -5; //modify variable b
    println!("new value of variable \"b\" is {}", b);

    //inferred type is i32 by default
    let c = 2_147_483_647;
    //signed integer 32bits = 4bytes - from -2,147,483,648 to +2,147,483,647
    //(4,294,967,295 states)
    println!("c = {}, size: {} bytes", c, mem::size_of_val(&c));

    let d: i16 = 32_767;
    //signed integer 16bits = 2bytes - from -32,768 to +32,767
    //(65,535 states)
    println!("d = {}, size: {} bytes", d, mem::size_of_val(&d));

    let e: isize = 123;
    //platform specific (win64) signed integer 64bits = 8bytes
    println!("e = {}, take up {} bytes on WIN {}-bit", e, mem::size_of_val(&e),
             mem::size_of_val(&e) * 8);

    let f: char = 'a'; // UTF-32 (32bits = 4bytes)
    println!("f = '{}', size: {} bytes", f, mem::size_of_val(&f));

    let g = 5.5;
    //signed double 64bits = 8bytes - inferred by default
    println!("g = {}, size: {} bytes", g, mem::size_of_val(&g));

    let h: f32 = 6.6;
    //signed float 32bits = 4bytes
    println!("h = {}, size: {} bytes", h, mem::size_of_val(&h));

    let i = true;
    //boolean 8bits = 1byte
    println!("i = {}, size: {} bytes", i, mem::size_of_val(&i));
}

//Type Explicit Conversions and Suffixes:
let _a: i16 = -150;
let _b = -150 as i16;
let _c = -1500i16;
let _c = -1500_i16;

вторник, 10 декабря 2019 г.

Cargo package manager

Cargo Book
The Rust build tool and package manager.

build your project with:
> cargo build
> cargo build --release
run your project with:
> cargo run
> cargo run --release
test your project with:
> cargo test
build documentation for your project with:
> cargo doc
> cargo doc --open
> cargo doc --open --no-deps
delete targets
> cargo clean

publish a library to crates.io with:
> cargo publish

Cargo also provides a command called cargo check. This command quickly checks your code to make sure it compiles but doesn’t produce an executable:
> cargo check
 
Cargo, please create a new project called hello I can run as a binary application:
> cargo new --bin hello
Cargo, please build and run it:
> cd hello
> cargo run
Output:
>> Hello, world!

 hello
  ├── Cargo.toml
  └── src
           └── main.rs

Filename: main.rs
fn main() {
    println!("Hello, world!");
}

Filename: Cargo.toml
[package]
name = "hello"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
edition = "2018"

[dependencies]

  
When your project is finally ready for release, you can use cargo build --release to compile it with optimizations. This command will create an executable in target/release instead of target/debug
 
Cargo has two main profiles: the dev profile Cargo uses when you run cargo build and the release profile Cargo uses when you run cargo build --release. The dev profile is defined with good defaults for development, and the release profile has good defaults for release builds.

Cargo has default settings for each of the profiles that apply when there aren’t any [profile.*] sections in the project’s Cargo.toml file. By adding [profile.*] sections for any profile you want to customize, you can override any subset of the default settings. For example, here are the default values for the opt-level setting for the dev and release profiles:
 
Filename: Cargo.toml
[profile.dev]
opt-level = 0

[profile.release]
opt-level = 3
 
More info.

Install Rust

Rust
A language empowering everyone to build reliable and efficient software.

On Windows, download and run rustup-init.exe. It will start the installation in a console.
After this, you can use the rustup command to also install beta or nightly channels for Rust and Cargo.

It is default option to install Rust. Installers are still available, but you should use rustup if possible, because it makes it easy to update and to use multiple toolchains at once (e.g. stable, beta and nightly). If you must use the installer, just select the x86_64-pc-windows-gnu installer and follow the step from the Using Rust on Windows page. If you're using rustup, read on.

By default, rustup on Windows installs the compiler and tools targeting the MSVC toolchain (x86_64-pc-windows-msvc), rather than the GNU/MinGW-w64 toolchain. At the initial menu, select 2) Customize installation. When asked for a host triple, enter x86_64-pc-windows-gnu. Then make a choice for the other questions, then proceed with the installation.

For debugging in CLion we need GNU toolchain:
If rustup is already installed, then rerunning rustup-init won't actually install the requested toolchain. Instead, run:
> rustup toolchain install stable-x86_64-pc-windows-gnu
if you already have the MSVC-based toolchain.
 
Then run:
> rustup default stable-x86_64-pc-windows-gnu
to set the GNU-based toolchain as the default.

To install the MSVC-based toolchain:
> rustup toolchain install stable-x86_64-pc-windows-msvc

To set the MSVC-based toolchain as the defaul:
> rustup default stable-x86_64-pc-windows-msvc

If you have a previous version of Rust installed via rustup, getting new Rust is as easy as:
> rustup update stable

To uninstall Rust and rustup, run the following uninstall script from your shell:
> rustup self uninstall

To test that you have Rust and Cargo installed, you can run this in your terminal of choice:
> cargo --version
> cargo -V
> rustc --version
> rustc -V

The installer also includes a copy of the documentation locally, so you can read it offline:
> rustup doc

You can install a code formatting tool (Rustfmt) with:
> rustup component add rustfmt

and a linting tool (Clippy) with:
> rustup component add clippy

MSYS2:
Rustup will install the MinGW linker and platform libraries automatically (as part of the rust-mingw component) and refuses to let you remove them. If you prefer to use the MinGW linker and libraries you installed with MSYS2, you'll need to create a .cargo/config file (either in your profile directory, i.e. C:\Users\you\.cargo\config, or in your project's directory if this configuration is specific to a project). The contents of that file might look like this:

[target.x86_64-pc-windows-gnu]

linker = "C:\\msys2\\mingw64\\bin\\gcc.exe"
ar = "C:\\msys2\\mingw64\\bin\\ar.exe"

Rustup will modify the PATH environment variable unless you told it not to. However, MSYS2 resets PATH by default when you launch, so when you try to invoke cargo or rustc from your MSYS2 shell, it might not find it. You'll need to edit your .profile/.bash_profile script to set the PATH correctly (you need to prepend /c/Users/yourname/.cargo/bin: to PATH).
Original post

More info.