В марте 2015 года я начал небольшой пилотный проект по отправке кода Rust в браузере Firefox. Это был первый код Rust, поставляемый в Firefox, и первое развертывание кода Rust для сотен миллионов пользователей по всему миру. Это моя история.

Rust был (есть!) Новым языком, который начинался как побочный проект Mozilla и превратился в популярное сообщество программистов. Он призван соединить мир программирования приложений, критичных к производительности, с эргономикой и безопасностью языков сценариев. Я надеюсь, что мы наконец сможем заменить C ++ на что-нибудь получше. Но сначала мы должны посмотреть, сработает ли это.

Для начала нам нужно было что-то маленькое и легко возвращаемое, чтобы минимизировать ущерб, если что-то не сработает. И мы не хотели тратить время на переписывание полезного кода. Итак, мы выбрали демультиплексор mp4.

Разбор видео файлов в формате mp4.

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

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

Это был хороший разрешающий синтаксический анализатор, и его было легко интегрировать, поэтому он отвечал нашим непосредственным потребностям. Но по мере накопления опыта мы поняли, что это не сработает для нас. Код использует очень минималистичный подход, который хорош для обработки всех несовместимых файлов, с которыми можно столкнуться в Интернете. К сожалению, он также был минималистичным в проверке ошибок, поэтому мы заподозрили, что он полон уязвимостей безопасности. Как приложение на C ++, у нас даже не было преимуществ песочницы Android JNI.

Таким образом, мы заменили его по частям новым кодом на C ++, и к тому времени, когда мы искали пилотный проект Rust, мы использовали его только для анализа заголовка и отслеживания метаданных. У нас была библиотека с ограниченными возможностями, которую все равно хотелось переписать, и которая не вызывалась ни в каких внутренних циклах. Идеально.

Изучение Rust

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

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

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

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

Поддерживаемые архитектуры

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

Генерация кода была простой благодаря LLVM, но нужно было проделать большую работу на уровнях поддержки ОС стандартной библиотеки и правильной конфигурации.

Команда Rust оказала большую поддержку и проделала огромную работу по добавлению поддержки для Windows XP, Android, старых версий MacOS и связи с MS Visual Studio. Тем не менее, прошло почти шесть месяцев между выпуском нашей первой платформы (x86_64-linux) и нашей последней (armv7-android).

Системы сборки - это весело

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

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

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

Самый простой способ интегрировать код Rust в проект C или C ++ - использовать поддержку rustc (или cargo) для создания статических библиотек. Это свяжет весь код ржавчины и зависимые от него библиотеки в форму, которая может быть объединена с основной частью приложения, как любая другая библиотека C.

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

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

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

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

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

Нам еще предстоит решить некоторые проблемы. Например, использование функции «рабочие области» Cargo для совместного использования артефактов сборки при создании нескольких статических библиотек. Унифицированная сборка также усложняет изменение флагов конфигурации между разными разделами кода Rust, например, если один модуль хочет всегда включать отладочные утверждения, а другой - нет.

Обновление автоматизации сборки

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

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

Конечно, мы должны сделать это с помощью наборов инструментов C ++ и Java, платформенных SDK и так далее. Но здесь новизна и активное развитие Rust поставили новую задачу. Мы никогда не могли полагаться на его присутствие в базовых образах Docker или виртуальных машин, а новый, обратно совместимый со старым кодом, стабильный выпуск каждые шесть недель означал, что нам приходилось проверять и обновлять все это оборудование чаще, чем мы привыкли. Я заставил многих людей изрядно ворчать на этот счет.

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

Быстрый вывод здесь

Это были некоторые из основных препятствий, с которыми я столкнулся при запуске пилотного проекта с новым компилируемым языком в крупном программном приложении.

Сообщество Rust очень полезно и благосклонно. Они проделали огромную работу по развитию культуры участия, и теперь легко задавать вопросы, обращаться за помощью или вносить свой вклад в язык и инструменты. Было очень интересно увидеть, как мой начальный код на Rust был выполнен первый миллиард раз.

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

Работа, конечно, продолжается. Многие люди в Mozilla изучают этот язык. Коллеги (пере) пишут разные утилиты на Rust. Мы объединяем части экспериментального серво движка браузера в Firefox.

Мы создаем дополнительную инфраструктуру для поддержки копий программных пакетов Rust в дереве исходных кодов Firefox. Cargo упрощает обмен сторонним кодом, но мы не хотим, чтобы наша работа блокировалась во внешних репозиториях, и иногда нам нужно вносить изменения. Есть интересные идеи об автоматической проверке лицензий и установке соответствующих требований проверки кода для разных частей.

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