Одна из непроговоренных особенностей Раста состоит в том, что функция - это big deal. В питоне почти любой кусок кода можно вынуть и положить в функцию. После появления yield from, таких кусков стало ещё больше. Фактически, в питоне нельзя вынести в отдельную функцию только control flow (return/break/continue) внутри блоков. Всё остальное - можно.
В Rust функция, хоть и является существенно более дешёвой (из-за того что у функции неиллюзорные шансы быть inline), вынесение кода в функцию не всегда возможно. Функции нужны сигнатуры, а некотрые сигнатуры чертовски тяжело написать, и даже если это возможно, сложность "думания" в этот момент в разы выше. В Расте постепенно появляются всякие безумия с GAT'ами, которые расширяют список того, что может быть функцией ... точнее, методом, но общий принцип раста - не всё возможно.
В целом, я бы сказал, что это именно набор ограничений. Берём условный язык опасно близкий к ассеблеру, натягиваем на него вкусный синтаксический сахар, который его превращает в язык высокого уровня, а потом запрещаем крупными мазками "чаще всего плохие вещи". В замен мы получаем песочницу для программиста (где большая часть опасностей предотвращена), массу подсказок для компилятора для оптимизации последствий песочницы. Но ещё при этом получаем запрет на, казалось бы, нормальные вещи. Некоторые из которых - скрытая опасность (и за это хвалят Rust), а некоторые - вполне себе sound код, но "ну так получилось".
С каждой версией в Rust'е всё больше "ну так получилось" убирают, добавляя механизмы для sound & safe реализации новых нюансов.
Но сам принцип "denied by default" остаётся, то есть с точки зрения программиста Rust куда более ограничивающий язык, чем большинство языков низкого уровня и языков с расслабленной типизацией (к которой относится и Питон).
Соответственно, это приводит к двум отношениям с языком:
Пишу как хочу и не думаю, если ошибусь, компилятор поймает (и может даже, предложит исправление). Это основной режим, no thinking required. Точнее, думать можно уже не о языке, а о решаемой проблеме, а всю рутину язык либо сам сделает. Это выведение типов, AsRef/Borrow/Copy трейты, generic'и, которые "сами всё делают", как .collect(). Последний я как раз считаю ярчайшим примером "магии раста". Итератор сам превращается в нужный тип, причём практически любой возможный.
Второе отношение с языком: "да что за хрень?". ~70% этой хрени по делу, то есть компилятор конкретно запрещает делать unsound вещи (например, менять вектор, по которому итерация, или вызывать второй раз FnOnce функцию). Примерно 5-7% - это мелкие придирки (Пиши `foo as i64`, когда `foo: i32`) или шероховатости типов (например type(&str[0]) != char).
Оставшиеся 20-25% режима "что за хрень", это праведное возмущение, потому что я знаю, что так можно, а компилятор мне не верит, или я хочу, но не могу написать (сигнатуру для замыкания). С временем эта область подсокращается (чаще всего через сложные механизмы для тех, кто очень хорошо знает язык, те же GAT'ы), но в целом она есть и никогда не исчезнет.
Это сильно противоречит питону или C, где практически нет мест, где "нельзя написать". Можно, но последствия остаются на совести написавшего, а иногда бывает так, что написать можно, а написать sound (непротиворечиво) нельзя, и языку пофиг.
(уклонился от исходной темы).
Так вот, функции в rust - это big deal. Если пишешь функцию, это контракт, интерфейс, на написание которого может уйти больше времени, чем на сам код (самой функции и кода, который эту функцию вызывает). Более того, однажды написанный контракт (сигнатура функции) потом давлеет, и шаг влево/шаг вправо вызывает необходимость изменять сигнатуру функции, что автоматически приводит к необходимости думать про все места использования функции... Это частично обходится generic'ами, но generic'и повышают уровень "думания", и часто уводят в те самые углы, где "компилятор неправ" (например, если он выбирает неправильный метод построения generic'а и не хочет вывести типы корректно, то есть ругается при компиляции).
С другой стороны, когда знаешь, что пишешь, написанные сигнатуры - 80% программы. Дальше каждый кусок пишется под сигнатуру в режиме "no thinking required". Если оно по типам сходится, значит всё в программе правильно. Может быть ошибочный алгоритм (об этом думать надо), но код функции точно делает то, что написано в сигнатуре.
Это очень сильно меняет процесс программирования, особенно, если кода много. Я ещё не дошёл до того уровня, в котором могу планировать модули (не хватает интуиции и практики), но написать парочку трейтов для структуры ещё до написания полей самой структуры - это да, это совершенно другой уровень программирования.
В Rust функция, хоть и является существенно более дешёвой (из-за того что у функции неиллюзорные шансы быть inline), вынесение кода в функцию не всегда возможно. Функции нужны сигнатуры, а некотрые сигнатуры чертовски тяжело написать, и даже если это возможно, сложность "думания" в этот момент в разы выше. В Расте постепенно появляются всякие безумия с GAT'ами, которые расширяют список того, что может быть функцией ... точнее, методом, но общий принцип раста - не всё возможно.
В целом, я бы сказал, что это именно набор ограничений. Берём условный язык опасно близкий к ассеблеру, натягиваем на него вкусный синтаксический сахар, который его превращает в язык высокого уровня, а потом запрещаем крупными мазками "чаще всего плохие вещи". В замен мы получаем песочницу для программиста (где большая часть опасностей предотвращена), массу подсказок для компилятора для оптимизации последствий песочницы. Но ещё при этом получаем запрет на, казалось бы, нормальные вещи. Некоторые из которых - скрытая опасность (и за это хвалят Rust), а некоторые - вполне себе sound код, но "ну так получилось".
С каждой версией в Rust'е всё больше "ну так получилось" убирают, добавляя механизмы для sound & safe реализации новых нюансов.
Но сам принцип "denied by default" остаётся, то есть с точки зрения программиста Rust куда более ограничивающий язык, чем большинство языков низкого уровня и языков с расслабленной типизацией (к которой относится и Питон).
Соответственно, это приводит к двум отношениям с языком:
Пишу как хочу и не думаю, если ошибусь, компилятор поймает (и может даже, предложит исправление). Это основной режим, no thinking required. Точнее, думать можно уже не о языке, а о решаемой проблеме, а всю рутину язык либо сам сделает. Это выведение типов, AsRef/Borrow/Copy трейты, generic'и, которые "сами всё делают", как .collect(). Последний я как раз считаю ярчайшим примером "магии раста". Итератор сам превращается в нужный тип, причём практически любой возможный.
Второе отношение с языком: "да что за хрень?". ~70% этой хрени по делу, то есть компилятор конкретно запрещает делать unsound вещи (например, менять вектор, по которому итерация, или вызывать второй раз FnOnce функцию). Примерно 5-7% - это мелкие придирки (Пиши `foo as i64`, когда `foo: i32`) или шероховатости типов (например type(&str[0]) != char).
Оставшиеся 20-25% режима "что за хрень", это праведное возмущение, потому что я знаю, что так можно, а компилятор мне не верит, или я хочу, но не могу написать (сигнатуру для замыкания). С временем эта область подсокращается (чаще всего через сложные механизмы для тех, кто очень хорошо знает язык, те же GAT'ы), но в целом она есть и никогда не исчезнет.
Это сильно противоречит питону или C, где практически нет мест, где "нельзя написать". Можно, но последствия остаются на совести написавшего, а иногда бывает так, что написать можно, а написать sound (непротиворечиво) нельзя, и языку пофиг.
(уклонился от исходной темы).
Так вот, функции в rust - это big deal. Если пишешь функцию, это контракт, интерфейс, на написание которого может уйти больше времени, чем на сам код (самой функции и кода, который эту функцию вызывает). Более того, однажды написанный контракт (сигнатура функции) потом давлеет, и шаг влево/шаг вправо вызывает необходимость изменять сигнатуру функции, что автоматически приводит к необходимости думать про все места использования функции... Это частично обходится generic'ами, но generic'и повышают уровень "думания", и часто уводят в те самые углы, где "компилятор неправ" (например, если он выбирает неправильный метод построения generic'а и не хочет вывести типы корректно, то есть ругается при компиляции).
С другой стороны, когда знаешь, что пишешь, написанные сигнатуры - 80% программы. Дальше каждый кусок пишется под сигнатуру в режиме "no thinking required". Если оно по типам сходится, значит всё в программе правильно. Может быть ошибочный алгоритм (об этом думать надо), но код функции точно делает то, что написано в сигнатуре.
Это очень сильно меняет процесс программирования, особенно, если кода много. Я ещё не дошёл до того уровня, в котором могу планировать модули (не хватает интуиции и практики), но написать парочку трейтов для структуры ещё до написания полей самой структуры - это да, это совершенно другой уровень программирования.