Все мы привыкли, что написанный код отлично поддаётся отладке и неважно на чём писать. Однако Rust заставил меня поднапрячься в этом вопросе, о чём я и спешу с вами поделиться.
Преамбула
Некоторое время назад, я отказался от полновесных IDE (предпочитая Sublime Text или Neovim) и на то есть две причины: во-первых, меня это делает более расслабленным в плане настройки: всё работает из коробки; во-вторых, это не создаёт повода разобраться в том, как всё устроено и засчёт чего работает, что приводит к возникновению профессиональных пробелов).
Проблема пришла откуда не ждали: все крутые и современные инструменты разработки предоставляют встроенный отладчик, а когда я взялся за Rust, быстро оказалось, что у меня нет инструмента для вдумчивой отладки.
Не то, чтобы он не существовал, просто я им не владел. И решил исправиться. Выбор пал, конечно, на отладчик GDB. Он, как раз, поддерживает Rust. Думаю, как скачать и установить отладчик вы разберётесь. Но, если по какой-то причине вам этого не то, чтобы сильно хочется делать, можете воспользоваться поставляемым с Rust - rust-gdb (в принципе, как я понял, тот же самый отладчик, просто устанавливается заодно; по крайней мере разницы не заметил).
Кстати, по какой-то причине, rust-gdb у меня работал хуже, чем gdb: ему не хватало какого-то файла и изнутри кода он отказывался показывать листинги. Потом выяснилось, что листинги терялись после завершения первого цикла отладки. И в GDB тоже.
Для начала пишем код
Я до этого никогда не пользовался GDB (только немного читал про него в какой-то книжке с примерами о безопасности приложений), но уже того, что я о нём знал - привело меня к мысли о том, что это крепкий орешек.
Для начала я написал небольшой кусочек кода, который буду исследовать. Всего 3 файла с простейшими функциями. Собираю проект в режиме отладки:
cargo build
Приложение собралось и теперь версия с текстом для отладки находится внутри. Начинается самое непривычное.
Запуск отладчика
У меня проект называется debug_test, так что запускаем в терминале:
gdb target/debug/debug_test
Сперва, посмотрим лист приложения (оно ещё не запущено). Для этого пишем команду: list (есть сокращённые версии команд, но я буду использовать полные).
Итак, мы посмотрели на код, давайте поставим точку остановки, откуда будем код изучать. Ставить точку остановки можно на номер строки, по названию функции, а ещё точка остановки может быть одноразовой, условной или выполняться по регулярному выражению: ну вы поняли уже, что тут без поллитра справки разобраться будет непросто. Но мы ограничимся, пока что, самым простым. Ставим точку остановки на функцию: break main.
И запускаем всё: run.
Теперь есть два пути: просто выполнить функцию или зайти в неё. Если выполним (команда next), то на отладку мы не посмотрим, а вот если зайдём внутрь (команда step) - окажемся внутри функции:
Теперь мы находимся где-то внутри функции, но непонятно где. Давайте осмотримся:
Сейчас много чего можно сделать: посмотреть состояние регистров или стека, однако это не то, что нам сейчас надо. Давайте поглядим, какое значение у переменной х: print/d x.
Тут надо объяснить: просмотр переменных может выполняться в различных форматах: двоичном, восьмеричном и прочих (у меня по-умолчанию - десятеричный). Формат задаётся через косую черту: x - значит HEX, то есть шестнадцатеричный, o - значин octo, то есть восьмеричный. Подробности - в руководстве, не станем останавливаться на этом.
Итак, мы увидели значение. Давайте поставим ещё одну точку остановки - на level2::run_it.
Кстати, если я, скажем, заблудился и забыл, где нахожусь, то можно просто использовать команду frame, которая показывает содержимое текущего кадра стека и, заодно, строку, где мы находимся:
Чтобы зайти внутрь функции level2::run_it - достаточно просто использовать команду step - она позволяет зайти внутрь. Единственное, пока не понятно, каким принципом руководствуется отладчик для выбора, в какую функцию зайти. Но с этим можно разобраться позже.
На рисунке выше виден исходник функции run_it и мы на 5 строчке. Давайте попробуем опять воспользоваться командой step, чтобы зайти внутрь.
Видим, что отладчик выбрал для входа первую функцию слева. Если продолжать - то зайдёт и во вторую. Там можно посмотреть значения переменных или содержимое стека: команда backtrace.
Кстати, обратите внимание: те переменные, что мы смотрели ранее - получают что-то вроде псевдонима (например $4): он позволяет обратиться к переменной постфактум.
Ради интереса, попробуем изменить значение x на другое: тут нам поможет команда set <переменная>=<значение>.
Пишем команду finish (чтобы выйти из функции) или continue (чтобы продолжить выполнение) и смотрим на результат. А результат таков, что наши изменения никак не повлияли на результат программы: возвращаются те же значения.
Но проблема оказалась чисто техническая: объявленная переменная была запрещена к изменению, а добавленный модификатор mut (при условии отсутствия изменений переменной внутри кода) убирался оптимизацией компилятора.
На выводе сверху видно, что значения изменились: по умолчанию они должны быть, как на снимке ниже.
Ну, как-то так, разобрались.
Отладка в браузере
Кстати, отладку можно производить и в браузере (по крайней мере так заявляется). Запускаем:
gdbgui target/debug/debug_test -r --port 5000
Немного пояснений: встроенный rust-gdbgui работать у меня не захотел, оттого я установил просто gdbgui по инструкции и запустил с нужными мне параметрами, по другой инструкции.
Работает и, по большей части, управляется как обычный GDB, однако со свистелками и гуделками.
Ощущения, что всё сильно удобней - нет. Просто больше информации сразу выведено на экран.
Заключение
В общем и целом, отладка для Rust не просто существует, но и весьма доступная для использования, хотя подучить матчасть придётся. Кстати, у GDB довольно много поддерживаемых языков. Мало ли пригодится.