Rust через его основополагающие принципы

http://designisrefactoring.com/2016/04/01/rust-via-its-core-values/
  • Перевод

У меня есть несколько мыслей об изучении языков программирования.

Во-первых, мы подходим к этому неправильно. Я уверен, что вы испытывали такие же ощущения. Вы пытаетесь изучить новый язык и не совсем понимаете, как в нём всё устроено. Почему в одном месте используется один синтаксис, а в другом другой? Все эти странности раздражают, и в итоге мы возвращаемся к привычному языку.

Я считаю, что наше восприятие языков играет с нами злую шутку. Вспомните, как вы последний раз обсуждали новый язык. Кто-то упомянул о нём, а кто-то другой поинтересовался его скоростью, синтаксисом или имеющимся веб-фреймворком.

Это очень похоже на обсуждение автомобилей. Слышали о новом Ford Bratwurst? Насколько он быстр? Смогу ли я проехать на нём через озеро?

Когда мы похожим образом говорим о языках, то подразумеваем, что они взаимозаменяемы. Как машины. Если я знаю, как управлять Toyota Hamhock, значит смогу вести и Ford Bratwurst без каких-либо проблем. Разница только в скорости и приборной панели, не так ли?

Но представьте, как будет выглядеть PHP-автомобиль. А теперь вообразите, насколько будет отличаться автомобиль Lisp. Пересесть с одного на другой потребует гораздо большего, чем усвоить, какая кнопка управляет отоплением.

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

Синтаксис и скорость языка выражают его ключевые характеристики. Например, Ruby известен тем, что выше всего ценит "комфорт разработчика", и это повлияло на все его особенности. Java придаёт большое значение обратной совместимости, что также отразилось на языке.

Таким образом, моя следующая идея такова: лучше изучать язык через его ключевые особенности. Если мы поймём, почему в языке были приняты те или иные решения, будет проще понять, как именно он работает.


Давайте посмотрим на ключевые ценности Rust:


  • Скорость
  • Безопасность работы с памятью (memory safety)
  • Параллелизм (concurrency)

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

Стоит уточнить: в Rust под "безопасностью работы с памятью" подразумевается, что он не допустит ошибку сегментации (segmentation fault), которая знакома вам не понаслышке, если вы работали с С или С++. Если же вы (как и я) избегали этих языков, тогда это может быть непривычным. Представьте себе следующие ситуации:


  • обращение к пятому элементу массива из двух элементов
  • вызов метода на нулевом значении
  • одновременное изменение одной переменной двумя функциями, оставляющее её значение неопределённым
  • не освобождённая память

В Ruby вы можете получить исключение, но в таких языках, как C, случится нечто похуже. Возможно, ваша программа аварийно завершится. А может быть, она выполнит какой-то произвольный код, и ваша небольшая программа на С приведёт к гигантской уязвимости. Упс.

Под "безопасностью работы с памятью" в Rust подразумевается то, что такой проблемы не возникнет.
Примечание переводчика: Rust разрешает утечки памяти в безопасном коде и не может гарантировать их отсутствие в общем случае. Поскольку гарантировать отсутствие циклических ссылок для Rc/Arc (указателя со счётчиком ссылок) невозможно, функция forget не является "небезопасной" (unsafe). Логика понятна, хотя мне крайне не нравится — предпочёл бы чтобы эта функция всё-таки была unsafe, чтобы подчеркнуть, что с ней надо обращаться осторожно.

Ruby тоже защищает вас от ошибок сегментации, однако использует для этого сборщик мусора. Это здорово, но оказывает негативное влияние на производительность.

Но ведь Rust придаёт скорости большое значение! Следуя этой цели, Rust отказывается от сборщика мусора. Управление памятью — задача программиста. Подождите, а как же все эти ужасающие баги, которые я упоминал?! Так как Rust ценит скорость, он заставляет меня управлять памятью. Вот только если второй ключевой ценностью этого языка является безопасность работы с памятью, то почему он заставляет меня работать с ней вручную?!

Между этими двумя целями наблюдается явное противоречие. Помня об этом, давайте попробуем разобраться в Rust!


fn main() {
  let x = 1;
  println!("{}", x);
}

[Запустить]

Это одна из наиболее простых программ, которые можно написать на Rust.

Стремясь гарантировать безопасность работы с памятью, как вариант, вы можете запретить изменения данных.

Таким образом, в Rust всё по умолчанию неизменно (immutable).


fn main() {
  let x = 1;
  x = x + 1; // error: re-assignment of immutable variable `x` [E0384]
  println!("{}", x);
}

[Запустить]

Конечно же, создатели Rust хотят, чтобы люди пользовались их языком. Так что мы можем объявить переменные изменяемыми, если это реально необходимо.


fn main() {
  let mut x = 1;
  x = x + 1;
  println!("{}", x); // 2
}

[Запустить]

При помощи ключевого слова mut можно явно указать на то, что значение может изменяться. Явность — это четвёртая из главных особенностей Rust. Забавно, но я не видел, чтобы она вообще явно упоминалась среди целей языка, хотя при наличии выбора между явным и неявным, Rust обычно выбирает первое.

Вот только не создаст ли проблемы возможность изменять данные? Одна из целей языка — это безопасность работы с памятью. Изменяемость данных и безопасность работы с памятью кажутся взаимоисключающими.


fn main() {
  let mut x = 1;
  // передаём х в функцию, которая удаляет данные из памяти
  println!("{}", x); // и получаем проблему при попытке использовать х
}

[Запустить]

Оставаясь верным своим основным ценностям, Rust привносит новую идею — владение (ownership). В Rust каждое значение должно иметь одного владельца. И участок памяти, принадлежащий владельцу, освобождается, когда тот выходит из области видимости.

Давайте посмотрим, как это работает:


fn main() {
  let original_owner = String::from("Hello");
  let new_owner = original_owner;
  println!("{}", original_owner); // error: use of moved value: `original_owner` [E0382]
}

[Запустить]

Многословный синтаксис String::from создаёт строку, которой мы действительно владеем. Затем мы передаём владение, и в этот момент original_owner владеет… ничем. У нашей строки может быть только один владелец.

Через систему владения Rust обеспечивает безопасность работы с памятью. Если у данных может быть только один владелец, то возможность изменить их разными потоками управления одновременно исключается. По тем же причинам невозможно обратиться к данным, которые были уничтожены.

Ранее я говорил, что значения уничтожаются при выходе из области видимости. До сих пор мы имели дело только с одной областью видимости — нашей функцией main. В большинстве программ существует больше одной области видимости. В Rust области видимости ограничиваются фигурными скобками.


fn main() {
  let first_scope = String::from("Hello");

  {
    let second_scope = String::from("Goodbye");
  }

  println!("{}", second_scope); // error: unresolved name `second_scope` [E0425]
}

[Запустить]

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

Это ещё одна ключевая составляющая безопасности работы с памятью. Если у нас нет возможности обратиться к переменным, которые вышли из области видимости, значит мы уверены, что никто не сможет использовать удалённые данные. Компилятор просто не позволит этого.

Теперь мы понимаем немного больше о том, как устроен Rust:


  • У данных может быть только один владелец.
  • Переменные уничтожаются при выходе из области видимости.

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

Для начала о функциях. Мы объявляем их точно так же, как нашу функцию main:


fn same_length() {
}

fn main() {
  same_length();
}

[Запустить]

Наша функция same_length должна принимать два параметра: исходную строку и строку для сравнения.


fn same_length(s1, s2) { // error: expected one of `:` or `@`, found `,`
}

fn main() {
  let source = String::from("Hello");
  let other = String::from("Hi");

  println!("{}", same_length(source, other));
}

[Запустить]

Rust питает особую любовь к явности, поэтому мы не можем объявить функцию, не указав, какие данные будут в неё передаваться. Rust использует сильную статическую типизацию в сигнатурах функций. Таким образом, компилятор может убедиться, что мы применяем наши функции правильно, предотвращая ошибки. Явная типизация также помогает легко видеть, что функция принимает. Наша функция принимает только строки, что мы и укажем:


fn same_length(s1: String, s2: String) {
}

fn main() {
  let source = String::from("Hello");
  let other = String::from("Hi");

  println!("{}", same_length(source, other)); // error: the trait `core::fmt::Display` is not implemented for the type `()` [E0277]
}

[Запустить]

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


fn same_length(s1: String, s2: String) { // error: mismatched types: expected `()`, found `bool`
  false
}

fn main() {
  let source = String::from("Hello");
  let other = String::from("Hi");

  println!("{}", same_length(source, other));
}

[Запустить]

И снова о явности. Функции должны декларировать не только то, что они принимают, но и тип возвращаемого значения. Мы возвращаем bool:


#[allow(unused_variables)]
fn same_length(s1: String, s2: String) -> bool {
  false
}

fn main() {
  let source = String::from("Hello");
  let other = String::from("Hi");

  println!("{}", same_length(source, other)); // false
}

[Запустить]

Круто. Это компилируется. Давайте попробуем реализовать сравнение. У строк есть функция len, которая возвращает их длину:


fn same_length(s1: String, s2: String) -> bool {
  s1.len() == s2.len()
}

fn main() {
  let source = String::from("Hello");
  let other = String::from("Hi");

  println!("{}", same_length(source, other)); // false
}

[Запустить]

Здорово. Теперь произведём два сравнения!


fn same_length(s1: String, s2: String) -> bool {
  s1.len() == s2.len()
}

fn main() {
  let source = String::from("Hello");
  let other = String::from("Hi");
  let other2 = String::from("Hola!");

  println!("{}", same_length(source, other));
  println!("{}", same_length(source, other2)); // error: use of moved value: `source` [E0382]
}

[Запустить]

Помните правила? Может быть только один владелец, и после закрывающей фигурной скобки значения уничтожаются. Когда мы вызываем same_length, то передаём ей владение нашими строками, и по завершению этой функции они удаляются. Комментарии немного облегчат вам восприятие.


fn same_length(s1: String, s2: String) -> bool {
  s1.len() == s2.len()
}

fn main() {
  let source = String::from("Hello"); // source владеет "Hello"
  let other = String::from("Hi"); // other владеет "Hi"
  let other2 = String::from("Hola!"); // other2 владеет "Hola!

  println!("{}", same_length(source, other)); 
  // Мы передали `same_length` владение source и other,
  // и они были уничтожены после завершения этой функции

  println!("{}", same_length(source, other2));  // error: use of moved value: `source` [E0382]
  // source больше ничем не владеет
}

[Запустить]

Это выглядит как серьёзное ограничение. Хорошо, что Rust ценит безопасность работы с памятью так высоко, но стоит ли оно того?

Rust игнорирует наше недовольство и придерживается своих ценностей, вводя понятие заимствования (borrowing). Значение может иметь только одного владельца, но любое количество заимствующих. Заимствование в Rust обозначается символом &.


#[allow(unused_variables)]
fn main() {
  let original_owner = String::from("Hello");
  let new_borrower = &original_owner;
  println!("{}", original_owner);
}

[Запустить]

Ранее мы пытались реализовать это через передачу владения и потерпели неудачу, но с одалживанием всё получилось. Попробуем использовать такой же подход в нашей функции:


fn same_length(s1: &String, s2: &String) -> bool {
  s1.len() == s2.len()
}

fn main() {
  let source = String::from("Hello"); // source владеет "Hello"
  let other = String::from("Hi"); // other владеет "Hi"
  let other2 = String::from("Hola!"); // other2 владеет "Hola!"

  println!("{}", same_length(&source, &other)); // false
  // Мы только одалживаем source и other функции same_length, так что они не будут уничтожены

  println!("{}", same_length(&source, &other2)); // true
  // Мы можем одолжить source снова!
}

[Запустить]

Мы явно одолжили наши данные функции, которая явно говорит, что только одалживает их, а не забирает владение. Когда same_length завершается, то заканчивается и одалживание, но данные не уничтожаются.

Погодите, разве это не нарушает безопасность памяти, о которой мы столько говорили? Не приведёт ли это к катастрофе?


fn main() {
  let mut x = String::from("Hi!");
  let y = &x;
  y.truncate(0);
  // О нет! truncate удаляет нашу строку!
}

[Запустить]

Хм… нет. Из безопасности работы с памятью в Rust вытекают следующие правила:


  • Может быть только один владелец.
  • Вы можете одалживать данные любое количество раз, но не можете их менять.

Запустите приведённый выше код и увидите результат.


<anon>:4:3: 4:4 error: cannot borrow immutable borrowed content `*y` as mutable

Благодаря этим двум правилам, соблюдается безопасность работы с памятью, и это происходит без принесения в жертву скорости. Все эти правила проверяются на этапе компиляции, не влияя на скорость выполнения.

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

Если мои теории, упомянутые в начале статьи, верны, это введение в одалживание не получилось таким уж сложным. Надеюсь, это понятнее, чем если бы я сказал: "Rust позволяет одалживать данные, просто не забывайте использовать &, и всё будет хорошо". Надеюсь.

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

Поделиться публикацией

Комментарии 41

    +3
    Лично мне очень помог интерактивный туториал Rust by Example. Однако, стоит отметить, что этот туториал не для новичков в программировании, а для тех, кто уже хорошо владеет другими языками. Всё-таки, мало языков программирования, с которыми можно ознакомить в рамках одной статьи, вот, например, этот туториал по Rust включает в себя 20 глав. Тем не менее, я думаю, ваша статья тоже будет полезна многим.
      +3
      Ваш комментарий и статья автора заинтересовали меня и подтолкнули поближе познакомиться с Rust. Спасибо большое)
        +1
        Лично мне очень помог интерактивный туториал Rust by Example.

        Поддержу — написано весьма неплохо, жаль на русский перевода нет (или я что-то упустил?).

        Правда я читал параллельно с официальной книгой — временами так проще разобраться. Тем более, что для неё и перевод имеется.
          +2
          Есть перевод, но я не знаю насколько он полон и актуален
        +1
        Мне в этой статье только немного странно, зачем на примере строк это показывать, раз до &str дело не доходит и он даже не упоминается. И тут никак не объясняется, почему в одном месте (println!) мы используем просто строковый литерал, а в других местах мы делаем String::from. Лучше было бы тогда, как мне кажется, завести структуру с каким-нибудь u8 полем и ее мучать, а то String/&str — это известное больное место для знакомящихся с языком.
          +1
          Про &String в clippy даже предупреждение есть: https://github.com/Manishearth/rust-clippy/wiki#ptr_arg
            +1
            Могу только предположить, что автор не стал заморачиваться. Вероятно, для структуры сложнее было бы придумать условно полезную функцию. (:

            Опять же, кто-нибудь мог бы придраться: мол такую мелкую структуру можно и копированием передавать.
            –2
            Очень интересный язык, но синтаксис удручает, лучше пошли бы по пути D или C#.
              +7
              Мне кажется, вся статья как раз и намекает, почему синтаксически ржавчина идет не по "пути D или C#". У языков приоритеты сильно отличаются.
                +2
                Давайте разберём на примерах? (:

                Потому что, например, сопоставление с образцом мне в Rust нравится больше. Хотя не буду спорить с тем, что у последнего синтаксис в общем-то более «замусоренный». Вот только это вынужденное решение. Скажем, много визуального шума привносит указание лайфтаймов, но ни в C# ни в D их просто нет. Аналогично с макросами, хотя мне и кажется, что они могли бы быть красивее, но уж лучше так, чем никак.
                  –1
                  Я тоже согласен с автором оригинального комментария, даже вот смотря на код приведенный в статье, и не углубляясь в дебри языка, поскольку его я пока не знаю:
                  String::from — почему не точка, ведь это 2 символа против одного;
                  fn same_length(s1: &String, s2: &String) -> bool — тут очень похоже на делфи, кроме "->", но все равно даже объявление функции выглядит, как вы уже сказали, несколько «замусоренным»
                  Хотя это мелочи и, возможно, действительно будет выглядеть оправданно при более детальном изучении
                    +5
                    почему не точка, ведь это 2 символа против одного;

                    Чтобы разделить доступ к объекту и чему-то внутри "области видимости". Можно спорить о пользе такого разделения, но мне как С++ программисту вполне привычно.
                    но все равно даже объявление функции выглядит, как вы уже сказали, «замусоренным»

                    А вот тут не соглашусь. Покажите "исправленный" вариант, потому что я не вижу что тут особо сделать можно не потеряв в чём-то другом.

                    Скажем, мы можем убрать амперсанды, если введём семантику передачи по ссылке, но это плохо живёт без GC и противоречит тому, что Rust пытается добиться. Ну или указание типа — можно сделать это перед именем переменной, как в куче других языков, но в Rust такой синтаксис работает везде и это хорошо для единообразия. То есть, мы или получим кучу разных правил для отдельных случаев или "мусор" просто перенесётся из этого места в, скажем, сопоставление с образцом.

                    Разве что от ключевого слова "fn" можно избавиться, но и тут не всё так просто. А уж такое указание возвращаемого типа есть и в С++ и выглядит оно менее изящно из-за необходимости писать auto перед именем функции.
                      0
                      А что скажете про двоеточие после имени входного параметра?
                        +4
                        Упрощает грамматику языка, убирает неоднозначности. Слоты аргументов в сигнатуре создают контекст сопоставления (аналогично с let), так что не надо вводить лишнюю сущность в грамматику.
                        Так что можно писать, если вдруг надо, всякое странное:
                        struct S { a: u8, b: u8, c: u8 }
                        
                        fn f(S{c, ..}: S) {
                            println!("c={}", c);
                        }

                        https://play.rust-lang.org/?gist=dc15c72441ad9d9371f68080790637e5
                        Ну и не так мало людей считают, что так читаемость улучшается, но это уже вкусовщина.
                          +3
                          Как по мне, двоеточие визуально отделяет имя параметра от его типа, что есть хорошо. Если вы ссылаетесь на golang, то там для такого различения требуется немного «приморгаться».
                            +4
                            Опять же, единообразие:
                            let a = 10i32;
                            let b: i32 = 10;
                            let (c, d) = (10, 20);
                            let (e, f): (i32, i32) = (10, 20);

                            Обойтись без let не получится: всё равно придётся вводить что-то типа плюсового auto. Первые два случая выглядят более-менее нормально:
                            auto a = 10;
                            int b = 10;

                            Но как быть с остальными? Опять же, let вполне естественно используется в конструкции if let. Не уверен, что придать такой же смысл auto удалось бы. В итоге придётся вводить дополнительный синтаксис.
                              +5
                              > А что скажете про двоеточие после имени входного параметра?

                              Аболютно стандартная нотация, общепринятая в литературе по computer science, языкам программирования, теории типов; используется во многих современных ЯП. Это *не* «как в Дельфи/Паскале», это «как везде кроме Си-подобных языков».
                        +2
                        Очень спорно, поинтересуйтесь, для C-like синтаксиса сложно написать адекватный парсер.
                        +1
                        Все больше смотрю в сторону этого языка. Интересно насколько возможно полуавтоматическое портирование с java, или C. Для высоканагруженых веб систем, было бы идеальное решение. Быстро, безопасно.
                          +2
                          Интересно насколько возможно полуавтоматическое портирование с java, или C.

                          В идиоматичный код, наверное, не возможно. Все-таки система владения/одалживания требует специфической структуризации кода.
                            0
                            > Интересно насколько возможно полуавтоматическое портирование с java, или C.

                            Насчёт джавы точно сомневаюсь, да и с С, подозреваю, будут проблемы. Всё-таки язык «заставляет» писать несколько по другому.

                            С другой стороны, если ничего не путают, то RustType изначально как раз прямо портировали с stb_truetype. Правда вручную. К сожалению, не знаю насколько процесс был трудоёмким.
                              0
                              про джаву, я не добписал коментарий. Насколько я знаю в rust будет добавляться garbage collector, таким образом портирование уже не будет таким уж сложным.
                                0
                                > таким образом портирование уже не будет таким уж сложным.

                                Вероятно, да. Вот только если использовать его для всего, то вряд ли что-то выиграем. Всё-таки в джаве GC развивают и уделяют этому немало внимания, а в Rust — это просто решение отдельной проблемы, а не ключевая особенность языка. Да и ресурсы разработчиков этих языков не сопоставимы.
                                  +1
                                  > Насколько я знаю в rust будет добавляться garbage collector

                                  Меня эта перспектива очень смущает до сих пор, все надеюсь что откажутся. Не верю я, что на такой стадии получится гладко в язык вписать GC (статьи про текущий попытки это сделать лично мне ужасают количеством тонкостей) и сильно побаиваюсь разделения crates.io на два лагеря и вытекающие из этого сложности.
                                    –1
                                    вообще с одной стороны GC нужен, тк есть огромный клас задач не требовательных к ресурсам. Да и некоторые алгоритмы значительно легчи написать с GC. В тоже время я разделяю ваши опасения, нужно его добавить так, чтобы он был лаконичен в языке и не подразумевал его повсеменое использоване.
                                      +1
                                      вообще с одной стороны GC нужен, тк есть огромный клас задач не требовательных к ресурсам. Да и некоторые алгоритмы значительно легчи написать с GC.

                                      Как по мне, если для удобного решения задачи Rc не хватает, то лучше такое на другом языке это дело и написать.
                                      0
                                      Насколько я помню GC ставится как отдельный модуль, и никто не мешает им не пользоваться, если не нужно. Но сама возможность его наличия — почему бы и нет? У GC хватает преимуществ вроде удобного менеджера памяти. Платить нужно за это только на этапе сборки, но иногда плюсы покрывают минусы.
                                        0
                                        например при создании gui, объектов мало, зависимости простые, ручное управление памятью просто напросто излишни
                                    +1
                                    RustType, как я понимаю, написан с нуля на ржавчине, просто с сильным поглядыванием на структуры и алгоритмы stb_truetype. А потом проект несколько в сторону уходит и своих фишек уже добавляет.

                                    А вот github.com/PistonDevelopers/truetype — это именно порт stb_truetype. У них там, особенно если в историю залезть, вообще все в unsafe и сырых указателях, которые они мееедленно вычищают. И до сих пор не вычистили. Так что портирование подобного сишного кода выглядит довольно трудоемко.
                                      0
                                      А вот github.com/PistonDevelopers/truetype — это именно порт stb_truetype.

                                      Перепутал всё-таки значит: из-за очень похожих названий сложно нагуглить. Помню читал какой-то "промежуточный отчёт" о портировании и тоже показалось, что это не такое и простое дело.
                                  +1
                                  >Вы можете одалживать данные любое количество раз, но не можете их менять.
                                  Через Mutex можно. Скорее так: если вы все делаете правильно, раст гарантирует что в любой момент времени у вас только один владелец который может менять данные.
                                    +1
                                    Через Cell или RefCell скорее. Но, в любом случае, в статье же о работе по умолчанию говорится.
                                      0
                                      Скорее так: если вы все делаете правильно, раст гарантирует что в любой момент времени у вас только один владелец который может менять данные.

                                      С поправкой соглашусь, потому что если очень захотеть, то "можно" и через transmute. (:
                                      Я так понимаю, автор не хотел сразу вываливать на читателя все нюансы. Всё-таки статья для новичков.
                                      0
                                      Вот тут хорошее описание правил заимствования, там же есть про владение и время жизни:
                                      rurust.github.io/rust_book_ru/src/references-and-borrowing.html

                                      И вообще, хорошая книга для старта изучения Rust :)
                                        +2
                                        Это вообще самая главная документация по ржавчине, с ней стоит в любом случае ознакомиться всем интересующимся. Но там как раз во многих местах нет объяснения почему в языке сделано именно так как сделано. Если и есть, то очень краткое. Для полноценного выяснения причин часто приходится спрашивать в чатах-форумах всяких или заниматься RFC-археологией (там есть клевые секции с альтернативами), а вот есть мнение, что хорошее понимание причин позволяет лучше понимать и конечный результат.

                                        Хотя конкретно в этой главе, которую, если не ошибаюсь, уже кучу раз переписывали, мотивация не так плохо показана.
                                          +2
                                          а вот есть мнение, что хорошее понимание причин позволяет лучше понимать и конечный результат.

                                          Могу сказать, что прочувствовал это на себе. Вроде, кругозор не такой уж узкий — лиспом интересовался, про хаскель пару книг прочёл. Самое забавное, когда язык выглядит "совсем по другому", то и воспринимать его легче — нет такого, что каждая мелочь кажется непривычной (это делает язык целиком).

                                          А вот в расте постоянно возникало раздражение и вопросы "почему они сделали так?!!", но после более глубокого изучения вопроса, почти всегда приходит понимание и решение кажется правильным.

                                          Конечно, мне далеко не всё в языке нравится, но эти решения, по крайней мере, выглядят последовательными.
                                        +1
                                        Мне одному кажется, что автор оригинала статьи просто увидел новый язык, прочитал 2-3 главы и сразу написал статью? 0_о
                                          0
                                          У него в блоге имеются другие, более старые, статьи про раст, так что сомневаюсь.
                                            0
                                            Значит ощущения подвели. Просто на мой взгляд, немного странно на примере Hello World разбирать особенности языка и его философию.
                                              +4
                                              Просто на мой взгляд, немного странно на примере Hello World разбирать особенности языка и его философию.

                                              Если попытаться объяснить всё сразу, то у нас получится аналог официальной документации. Данная статья — (насколько я понимаю) про другое. Многие ведь действительно не идут дальше поверхностных оценок типа "фуу дурацкие let, почему не сделали как в С++". Автор же, как мне кажется, смог донести кое-какие принципы языка даже до тех, кто его впервые увидел. Пусть и на несколько дурацких примерах.
                                                0
                                                Не спорю, статья хорошая)

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

                                        Самое читаемое