вторник, 3 апреля 2018 г.

[prog.flame] Таки Rust -- это язык, где каждая закорючка имеет огромное значение

В последние дни пытаюсь читать вторую редакцию Rust book-а. Но не потому, что сильно воспылал желанием попрограммировать на Rust-е, а потому, что предлагают принять участие в обсуждении Rust-а и C++ на грядущей C++ CoreHard Spring 2018. Вот и пытаюсь слегка погрузиться в тему.

По ходу чтения складывается ощущение, что второе издание писали с прицелом на дебилов веб-разработчиков, которые кроме Ruby и JavaScript-а ничего не видели. Плюс еще и очень сильно ощущается маркетинговая составляющая. Мол, Rust -- это чуть ли не лучшее, что появилось в области разработки софта, и с Rust-ом ваши волосы всегда будут... ;) Возможно, что я ошибаюсь, но года три назад, когда я впервые начал к Rust-у серьезно приглядываться, у меня не было такого ощущения.

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

fn main() {
    let x = 1;
    let y = {
        x+1
    };
    println!("x={:?}, y={:?}", x, y);
}

Результат работы вполне ожидаемый: x=1, y=2

Добавляем всего одну точку с запятой:

fn main() {
    let x = 1;
    let y = {
        x+1;
    };
    println!("x={:?}, y={:?}", x, y);
}

И получаем уже принципиально другой результат: x=1, y=()

Это потому, что блок кода -- это выражение (expression). И если последняя строка кода в таком выражении не имеет завершающей точки с запятой, то именно эта строка (т.е. выражение в этой строке) будет определять тип и результат всего блока кода. А вот если последняя строка завершается точкой запятой, то у всего блока кода типом и результатом будет специальный пустой тупл (или пустая структура, что в случае Rust-а, как я понял монопенисуально).

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

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

Например, в Rust-е есть специальные частные случаи для сокращения кода по инициализации структур (примеры взяты отсюда). Обычный способ создания и инициализации экземпляра структуры:

fn build_user(email: String, username: String-> User {
    User {
        email: email,
        username: username,
        active: true,
        sign_in_count: 1,
    }
}

Кстати, обратите внимание на отсутствие точки запятой после объявления экземпляра User -- это важно (почему см.выше).

Но можно записать и так:

fn build_user(email: String, username: String-> User {
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}

Оказывается, что если у вас в локальном контексте уже есть переменная или аргумент функции, имя которой совпадает с именем поля структуры, то можно просто записать имя переменной и компилятор Rust-а поймет, что имя переменная с этим именем должна использоваться для инициализации поля с таким же именем. Так, немножко волшебного порошка синтаксической сладкой пудры :)

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

let user2 = User {
    email: String::from("another@example.com"),
    username: String::from("anotherusername567"),
    ..user1
};

Вот эта магия -- ..user1 -- это оно и есть.

Ну и напоследок еще одна штука, которая попадает в категорию "это нужно просто запомнить, потому что понять это невозможно" :)

В Rust-е многие вещи, которые в C, C++ или в Java являются statement-ами, оказываются expression-ами. Т.е. выражениями, возвращающими значение. Например, в Rust-е if -- это expression. И match (который паттерн-матчинг) так же является expression-ом. Собственно, в этом нет ничего непривычного. Вот что реально рвет шаблон, так это то, что внутри expression-а могут быть control flow statement-ы. Например, continue.

loop {
    ... // Сколько-то кода.
    let guess: u32 = match guess.trim().parse {
        Ok(num) => num,
        Err(_) => continue
    };
    ... // Еще много кода.
}

Т.е. мы хотим где-то внутри цикла определить константу, берем ее значение из результата match-а. Но в самом match-е мы делаем continue. Т.е. безусловный переход на самый конец цикла. Неплохой такой expression с такими неслабыми побочными эффектами :)

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


Если кто-то думает, что я являюсь хейтером Rust-а, то вы ошибаетесь. На самом деле мне сейчас совершенно фиолетово. Что Rust, что Java, что C#, что Haskell какой-нибудь. Это все какие-то соседние миры, которые как-то сами по себе существуют и оказывают на меня, как на C++ника, какое-то косвенное воздействие. Ну и пускай себе существуют.

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

Комментариев нет: