Я получил несколько комментариев к моим предыдущим сообщениям о Rust (на Medium и в моем русскоязычном ЖЖ). Я ценю ответы, они кое-что прояснили для меня в Rust.
Нет классов
Все связанные функции не принадлежат ни к какому классу. На самом деле в Rust нет классов. Связанные функции могут возвращать объекты (переменные) заданного типа, но они не являются экземплярами какого-либо класса, они просто имеют типы. Это небольшая проблема, которую нужно понять, поскольку я привык к идее Python о том, что «все является классом и экземпляром класса, даже сам класс является экземпляром, числом и даже None».
Мой следующий вопрос был «почему?» У Rust нет никаких исторических оснований для оправдания этого. Первый результат поиска в Google дал мне многое:
- Мало ответов на этот вопрос о каком-то клоне ТАК.
- Отличный пример для
enumв Rust (не уверен, актуален ли это для проблемы класса, но сам по себе отличный). Новое слово для изучения: DTS == Типы с динамическим размером.
Окончательный ответ:
«Во-первых: у Rust были классы. Они были удалены, потому что другие концепции были сочтены более подходящими ».
Это ответ на мой другой вопрос о реальном значении ::. Он не имеет ничего общего с классами и является просто оператором для навигации по пространствам имен. Python делает это с точкой.
Неизменяемая переменная вместо неизменного значения
В Rust есть неизменяемые переменные, а не неизменяемые значения (или, возможно, они оба). Я могу передать значение из неизменяемой переменной в изменяемую и изменить это значение.
… Это вызывает у меня вопрос: есть ли в Rust строковые значения только для чтения? Я вижу раздел .rodata в результирующем двоичном файле ... Если у меня есть это значение, могу ли я записать в них и вызвать segfault? (без слова «небезопасно»). Я расскажу вкратце.
- Мне нужно найти способ частично изменить строку (какой-то
.replace()). "Нашел". - Я напишу простой код для изменения переменной, передав значение из неизменяемой переменной в изменяемую и затем изменив его.
(В процессе написания я обнаружил, что ‘ и “ имеют разное значение в Rust, так как Atom по-разному выделил их).
… Нет, str::replace возвращает только измененную строку. Не интересно. Продолжайте поиски.
Внезапно появилась хорошая статья по следующему вопросу, который у меня был в предыдущей главе: Str VS String.
Я не понимаю этого достаточно, чтобы перефразировать, но я начинаю видеть какой-то смысл в этих вещах.
И я получил ответ на вопрос «заменить в статической строке» в процессе:
Я думаю, что & str предпочтительнее, если строка не будет изменена на месте. Например, здесь нет операций по замене одних символов на другие.
Монады или без монад?
Как мне все вместе говорят, в Rust нет монад. Но сопоставление с образцом вместе с Enum допускает такие конструкции, которые обычно выполняются с помощью монад. То, что я видел в производственном коде на Haskell, составляет 90% от Maybe с добавлением Either и Just. И Enum охватить эти случаи, насколько я могу сейчас понять (фактически еще не узнав об этом). Следовательно, с точки зрения пуристов в CS, в Rust нет монад. С практической точки зрения он имеет все свои практические преимущества. Никакого плавления мозгов не включено.
Тип возвращаемого значения для операторов управления потоком кода
Следующий большой вопрос, который у меня возник, связан с сопоставлением шаблонов с ветвями, указывающими на операторы управления потоком кода (break, continue). Помимо изменений потока кода (которые выглядят странно внутри match), какой у них тип? Олег Этеревский (который много занимается, отвечая на вопросы; большое спасибо за это), говорит, что «они возвращают любой тип, чтобы удовлетворить систему типов». Что ж, допустим, это правда. Но какую ценность они возвращают? Какое значение получает переменная при выборе этой ветви (с оператором)? Или поток кода организован таким образом, что эта переменная будет отброшена после перехода как неиспользуемая? Я сомневаюсь, что это так, поскольку переменная должна кому-то принадлежать, и право собственности необходимо вернуть. Как и с оператором return (я думаю, что он разрешен в ветке match, и он существует в Rust).
Это пока открытый вопрос.
Я попытался напечатать переменную после части «перерыв», и мне это удалось.
Переменная осталась нетронутой.
Решил обойти компилятор. Что я пробовал:
- Просто выведите переменную вне цикла. Получена ошибка:
not found in this scope. - Объявить переменную вне области видимости и присвоить значение. Получил это значение без изменений, как будто совпадение не было выполнено. Скучный.
- Объявить переменную вне области видимости без присвоения значения (я случайно научился объявлять переменные:
let ans: i32;). Получена ошибка:use of possibly uninitialized `ans`. - Решил задать значение И делать
breakв разделеmatch:Err(_) => {break; 3}. Получилwarning: unreachable expressionи старое значение.
Пока это выглядит так: match либо возвращает значение, либо выполняет управление кодом. Нельзя делать и то, и другое одновременно. Если это так, переменная сохранит старое значение, так как match. Звучит разумно для меня, и я очень уважаю, что использование «возможно неинициализированной» переменной является ошибкой. И было приятное предупреждение за недостижимый оператор в моем коде.
И я действительно чувствую запах C. Любой функциональный язык закричал бы от гнева за такую уловку. Но мне нравится это.
Strace на двоичном
Еще одна вещь, которую я забыл сделать, - это посмотреть strace минимальный двоичный файл Rust.
Python известен своей раздутой природой (количество системных вызовов поразительно, как я писал некоторое время назад, примерно 2 секунды на одно выполнение «openstackclient»).
Хорошо, давай посчитаем. Начнем с Python:
echo |strace python -c “open(‘/dev/stdin’).read()” 2>&1|wc -l 850
C выглядит намного лучше (то же самое с _29 _ / _ 30_):
echo |strace ./a.out 2>& 1|wc -l 35
Rust… (это для меня в новинку, мне нужно открыть и прочитать файл).
Я пытался найти рабочий код, но этот код плохо скомпилировался и пожаловался на то, чего я не понимаю:
| the trait `std::ops::Carrier` is not implemented for `()` | in this macro invocation
В любом случае, fopen / fread не имеет большого значения. Я пойду guessing_game.
echo |strace target/release/guess_game 2>& 1 |wc -l 121
Без --prefer-dynamic это около 111 (как для релизной, так и для отладочной версии), за вычетом нескольких строк вывода guessing_game. Всего около 100:
21 mmap 12 mprotect 9 read 9 open 9 close 8 fstat 7 access 5 rt_sigaction 5 munmap 3 sigaltstack 2 getrlimit 2 getrandom 2 brk 1 set_tid_address 1 set_robust_list 1 sched_getaffinity 1 rt_sigprocmask 1 readlink 1 exit_group 1 execve 1 arch_prctl
Python для сравнения: 273 открытых, 127 прочитанных, 118 fstat, 101 stat, 75 закрытых (!).
Rust имеет 21 mmap, с 12 mprotect, python - 25.
Но подождите, это был python2. Python3:
echo |strace python3 -c “open(‘/dev/stdin’).read()” 2>&1| wc -l 554
с 106 статистикой, 68 rt_sigaction, 31 mmap…
Ну, python3 немного лучше. На самом деле есть некоторые усилия сообщества по улучшению времени старта.
В любом случае, Rust хуже C по количеству системных вызовов начальной загрузки, но все же неплохо. В 5 раз лучше, чем «современный» Python.
Кстати: C имеет 6 mmaps, 4 открытия, 4 mprotect…
Я думаю, что стремление Rust к mmap можно объяснить большим списком разделов (31, как я насчитал в предыдущей записи в блоге).