В Ruby появилась поддержка WebAssemblу: что это значит?
Автор:honyaki
С выпуском версии 3.2 Ruby пополнил список языков программирования, поддерживающих WebAssembly. Небольшое на первый взгляд обновление может стать самым значительным изменением языка со времён Rails, так как теперь разработчики смогут работать не только с бэкендом. После портирования кода на WebAssembly его можно будет запускать где и как угодно — на фронтенде, встроенных устройствах, как бессерверные функции, вместо контейнеров или в граничных вычислениях. WebAssembly может превратить Ruby в универсальный язык программирования.
WebAssembly — это бинарный низкоуровневый формат инструкций, запускаемый на виртуальной машине. Этот язык задумывался как альтернатива JavaScript для запуска приложений в любом браузере с максимальной скоростью работы. Целью компиляции могут быть такие языки, как C, Go, Rust, а теперь и Ruby.
В 2019 году Wasm вошёл в стандарт W3C, что позволило разработчикам писать высокопроизводительные приложения для веба. Сам стандарт ещё развивается, а его экосистема растёт. Сейчас на WebAssembly обратил пристальное внимание фонд Сloud Native Computing Foundation (CNCF). Под эгидой СNCF разрабатывается ряд проектов.
Дизайн Wasm основан на двух принципах: портируемости и безопасности. Бинарный код Wasm запускается в любом современном браузере, в том числе на мобильных устройствах. С целью обеспечения безопасности программы на Wasm запускаются в изолированной безопасной для памяти виртуальной машине. Следовательно, такие программы не могут получить доступ к ресурсам системы: они не могут менять файловую систему или получить доступ к сети или памяти.
WebAssembly выводит портируемость на следующий уровень
Предположим, что вы хотите создать кросс-платформенное приложение для Linux, Windows и macOS. Как это можно сделать?
Можно использовать компилируемый язык программирования, к примеру С, и создавать бинарный код для каждой ОС.
Компилятор создаёт несколько исполняемых файлов.
Можно работать с подходящей средой выполнения кода и выбрать интерпретируемый язык, к примеру, JavaScript, или язык, компилирующий в байт-код, вроде Java.
Код компилируется в один универсальный байт-код формат, исполняемый на платформе через среду выполнения. На схеме показан процесс компиляции для Java и JRE на различных платформах
Код компилируется в промежуточное представление — байт-код. Для такой системы необходима среда выполнения или виртуальная машина на устройстве клиента.
А что, если на устройстве клиента есть контейнерная среда выполнения? Тогда можно создать образ Docker для каждой платформы.
Схема рабочего процесса контейнера. Код собирается в Docker. Формируется три образа: для Linux, для Windows и для ARM. Среда выполнения на устройстве клиента выбирает нужный образ и запускает его
Код компилируется в образы, зависящие от платформы. На устройстве клиента должна находиться среда выполнения контейнера, которая автоматически выбирает нужный образ.
Ранее Ruby-разработчикам приходилось отправлять пользователям код. Для запуска приложения клиентам нужно было устанавливать интерпретатор Ruby или получить его от разработчика.
Разработчики отправляют код. Клиентам нужно установить соответствующий интерпретатор, чтобы запустить приложение
Пользователи получают сам код, а для его запуска необходимо установить интерпретатор.
Такие подходы дают портируемость, но за неё приходится расплачиваться — разработчику нужно собирать, тестировать и отправлять клиентам различные образы. Иногда приходится включать в релиз соответствующую среду выполнения или указывать на необходимость установить её отдельно.
WebAssembly (сокращённо Wasm) выводит портируемость на новый уровень: с помощью Wasm можно создать единственный бинарник и запустить его на любом современном браузере.
Демонстрация работы WebAssembly. Код компилируется в единственный бинарный файл Wasm. Его можно запустить из любого браузера. Единый бинарный файл работает в Linux, macOS и Windows. Таким же образом работает и Ruby WebAssembly.
WebAssembly компилирует код в низкоуровневый ассемблер для веба; один и тот же бинарный файл Wasm можно без изменений запускать на любой, даже мобильной платформе.
Возможность запускать код на скорости нативного позволила разработчикам создать Figma и Google Earth, даже запустить в браузере Vim.
В Ruby появилась поддержка WebAssembly
Последний релиз Ruby содержит портированный на Wasm интерпретатор: можно запускать код на Ruby в браузере без бэкенда, напрямую.
Для начала работы портированного на Wasm Ruby нужна лишь пара строк кода. Скрипт скачивает ruby.wasm
и создаёт экземпляр интерпретатора в браузере. После этого текст с типом text/ruby
отправляется в программу WebAssembly.
<html> <script src="https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@0.5.0/dist/browser.script.iife.js"></script> <script type="text/ruby"> puts "Hello, world!" </script> </html>
Откройте инструменты разработчика, чтобы увидеть, что Ruby запущен из браузера без бэкенда. После скачивания ruby.wasm
никаких соединений нет.
В открытой консоли браузера написано «Hello, world!» Этот код запущен в Ruby WebAssembly
JavaScript считается лучшим для изучения языком, ведь он есть везде. Но с WebAssembly люди могут изучать Ruby и экспериментировать с ним в браузере. Результат выводится в консоль.
На вкладке «Sources» можно увидеть даже содержимое ruby.wasm
, дизассемблированное в текстовый формат:
Инструменты разработчика в браузере открыты на вкладке «Sources». В список источников входит папка Wasm с загруженным интерпретатором Ruby. Отображаемый текст — порт интерпретатора Ruby WebAssembly
Файл Wasm видно в инструментах разработчика.
Порт Wasm доступен в песочнице Ruby.
Король песочницы
Как уже говорилось, программы на Wasm запускаются в режиме песочницы в виртуальной машине, у которой нет доступа к другим частям системы. А значит, приложение на Wasm не имеет доступа к браузеру, файловой системе, памяти или сети. Для получения или отправки данных из песочницы понадобится JavaScript-код.
В этом примере показано, как читать вывод программы на Ruby и изменять страницу при помощи npm-пакета ruby-head-wasm-wasi:
<html> <script src="https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@latest/dist/browser.umd.js"></script> <script> const { DefaultRubyVM } = window["ruby-wasm-wasi"]; const main = async () => { const response = await fetch( "https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@latest/dist/ruby.wasm" ); const buffer = await response.arrayBuffer(); const module = await WebAssembly.compile(buffer); const { vm } = await DefaultRubyVM(module); vm.printVersion(); vm.eval(` require "js" luckiness = ["Lucky", "Unlucky"].sample JS::eval("document.body.innerText = '#{luckiness}'") `); }; main(); </script> <body></body> </html>
Этот же пакет используется для запуска кода на Ruby в проекте на Node, что позволяет смешивать Ruby и JavaScript на бэкенде. Для работы этого примера потребуется установка npm-пакета ruby-head-wasm-wasi
:
import fs from "fs/promises"; import { DefaultRubyVM } from "ruby-head-wasm-wasi/dist/node.cjs.js"; const main = async () => { const binary = await fs.readFile( // Подсказка: при необходимости замените бинарный файл на информацию об отладке, если вам требуется символизированная трассировка стека // (пока что только в ночной версии) // "./node_modules/ruby-head-wasm-wasi/dist/ruby.debug+stdlib.wasm" "./node_modules/ruby-head-wasm-wasi/dist/ruby.wasm" ); const module = await WebAssembly.compile(binary); const { vm } = await DefaultRubyVM(module); vm.eval(` luckiness = ["Lucky", "Unlucky"].sample puts "You are #{luckiness}" `); }; main();
Выполнение Ruby WebAssembly за пределами браузера
Основная задача Wasm — выполнение двоичного кода в браузере, но разработчики быстро осознали потенциал быстрого, безопасного и портируемого на любые устройства двоичного формата доставки программного обеспечения. Wasm может стать таким же значимым, как Docker. С его помощью можно сильно упростить развёртывание приложений на встроенных системах, в бессерверных функциях, вычислениях на периферии (edge computing, также граничные вычисления). Можно использовать Wasm в качестве замены контейнеров Kubernetes.
Для запуска приложения на Wasm вне браузера требуется соответствующая среда исполнения с виртуальной машиной WebAssembly и интерфейсами для базовой системы. Здесь существует несколько решений, самые популярные из которых — это wasmtime, wasmer и WAMR.
В репозитории Ruby располагается полный пример упаковки кода приложения в пользовательский образ Ruby.
Ограничения Ruby WebAssembly
Не забывайте, что Ruby WebAssembly разработан совсем недавно. Экосистема Wasm развивается очень быстро. А сегодня у Ruby Wasm есть ряд недостатков, которые значительно ограничивают возможности его применения в больших проектах:
- Нет поддержки потоков.
- Не работает порождение дочерних процессов.
- Не поддерживается сеть.
- Сборщик мусора может создавать утечки памяти.
- Гемы и модули доступны только при создании кастомного образа Wasm.
Будущее прекрасно
WebAssembly открывает захватывающий мир. С помощью Wasm Ruby разработчики могут отойти от бэкенда. По мере развития WebAssembly достигнет новых рубежей и Ruby, откроются возможности применения языка в граничных вычислениях и serverless-приложениях.
После выпуска последней версии Ruby разработчики могут начать экспериментировать с WebAssembly. Конечно, это лишь первый шаг; чтобы сложные приложения на Ruby можно было запускать с помощью Wasm, нужно ещё немало поработать.