С выпуском версии 3.2 Ruby пополнил список языков программирования, поддерживающих WebAssembly. Небольшое на первый взгляд обновление может стать самым значительным изменением языка со времён Rails, так как теперь разработчики смогут работать не только с бэкендом. После портирования кода на WebAssembly его можно будет запускать где и как угодно — на фронтенде, встроенных устройствах, как бессерверные функции, вместо контейнеров или в граничных вычислениях. WebAssembly может превратить Ruby в универсальный язык программирования. Подробности под катом, а практика в вебе — на нашем курсе по Fullstack-разработке на Python.


WebAssembly — это бинарный низкоуровневый формат инструкций, запускаемый на виртуальной машине. Этот язык задумывался как альтернатива JavaScript для запуска приложений в любом браузере с максимальной скоростью работы. Целью компиляции могут быть такие языки, как C, Go, Rust, а теперь и Ruby.


В 2019 году Wasm вошёл в стандарт W3C, что позволило разработчикам писать высокопроизводительные приложения для веба. Сам стандарт ещё развивается, а его экосистема растёт. Сейчас на WebAssembly обратил пристальное внимание фонд Сloud Native Computing Foundation (CNCF). Под эгидой СNCF разрабатывается ряд проектов.


Дизайн Wasm основан на двух принципах: портируемости и безопасности. Бинарный код Wasm запускается в любом современном браузере, в том числе на мобильных устройствах. С целью обеспечения безопасности программы на Wasm запускаются в изолированной безопасной для памяти виртуальной машине. Следовательно, такие программы не могут получить доступ к ресурсам системы: они не могут менять файловую систему или получить доступ к сети или памяти.


WebAssembly выводит портируемость на следующий уровень


Предположим, что вы хотите создать кросс-платформенное приложение для Linux, Windows и macOS. Как это можно сделать?


Можно использовать компилируемый язык программирования, к примеру С, и создавать бинарный код для каждой ОС.


Код скомпилирован в трёх форматах: ELF для Linux, PE для Windows и Mach для 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, нужно ещё немало поработать.


Спасибо за внимание и приятного ассемблирования! А на наших курсах вас ждёт полезная теория и интересная практика:




Комментарии (8)


  1. OpenA
    00.00.0000 00:00
    +3

    Вот так при помощи нехитрого кода на жс, строки на руби компилируются в байткод vm и запускаются. Но зачем?

    Руби язык поддерживающий ввод/вывод в терминал, работу с фс и другими системными вещами из коробки.

    JS язык поддерживающий WebAPI и другие средства браузера из коробки

    в таком виде ни руби свои возможности использовать не может, ни джавоскриптовыми пользоваться в полной мере, практическая применимость вебассемблера все еще остается под вопросом.


    1. Akuma
      00.00.0000 00:00

      Практическая применимость - это скорость и порт какой-нибудь очень нужной и незаменимой (либо лень) библиотеки в веб.


      1. OpenA
        00.00.0000 00:00

        Ну скорости с ним точно не будет поскольку там байткод виртуальной машины, а не нативный код процессора. Может быть даже будет замедление поскольку у JS в отличии от джавы типы не определены, и их подставляют на этапе компиляции из абстрактного синтаксического дерева, в том числе их можно заменить на основе данных о реальном исполнении. В байткоде же (если википедия не врет) уже стек-машина с операциями на конкретный размер и тип данных (64 бита знаковое) тут уже у vm связаны руки.

        порт какой-нибудь очень нужной и незаменимой (либо лень) библиотеки в веб

        Еще было бы здорово использовать единый джаваскриптовый VM / JIT / GC для всех скриптовых языков в одном проекте например.

        Но к сожалению vm языков привязаны к функционалу, который гарантирован стандартом из коробки. Поэтому и библиотеку можно будет собирать не любую а только ту которая может работать в окружении js варианта web.


        1. Akuma
          00.00.0000 00:00
          +1

          Я прям тесты не проводил, но первые же страницы гугла выдают

          Wasm is 1.15-1.67 times faster than JavaScript on Google Chrome on a desktop. Wasm is 1.95-11.71 times faster than JavaScript on Firefox on a desktop. Wasm is 1.02-1.38 times faster than JavaScript on Safari on a desktop

          Просто использовать его надо не для работы с DOM, а для CPU-bound задач.

          Про Ruby не скажу, но в том же Rust очень много его собирается под wasm. Это позиционируется одной из фишек языка. Само-собой совсем уж любую библиотеку собрать не получится, но все же библиотек довольно много. Взять те же GUI, как хороший и сложный пример. Для нативного приложения (причем под любую платформу) они рисуют гуй в окошечках системы. При сборке в wasm - в webgl. И это все работать прям шустро.

          Пример: https://yew.rs

          Даже playgroud Rust-а есть в браузере.

          Или вот получше: https://www.egui.rs/#demo


          1. OpenA
            00.00.0000 00:00

            Просто использовать его надо не для работы с DOM, а для CPU-bound задач.

            Я тоже об этом, и wasm никак не поможет сделать производительные FLIF BPG или аудиоформаты с архиваторами на стороне клиента, ведь это не библиотека на си а просто байткод js прекомпилированный.

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

            они рисуют гуй в окошечках системы. При сборке в wasm - в webgl. И это все работать прям шустро.

            Это все рисуется в Canvas2D (webgl это уже Canvas3D ) естественно оно быстрое потому что функции написаны на нативном языке и оптимизованы/векторизованы как положено. Но вообще ты прав, перенос из Unity становится лучше с WebAsm, ведь с-sharp типизированный язык и компилировать его в скрипты это просто было бы уродством. Но вот другие скриптовыя языки (питон, руби, пхп, луа) я бы предпочел пересобирать в js по примеру typescript -а, что бы программа оставалась динамичной и не была привязана к типам.


            1. Akuma
              00.00.0000 00:00
              +1

              Делать архиваторы на стороне клиента это в целом так себе идея, поскольку работа с файлами в браузере никакая.

              Конечно все это довольно узко направленная штука, но что уж поделать.

              Да, там канвас. На днях с какой-то другой библиотекой копался, там именно 3D было. И окна и 3d, все в одном. Не суть.

              Кстати да, с играми хороший пример.

              Конечно все это выглядит как "поиграться" и каких-то крупных нашумевших проектов, использующих что-то подобное я на память и не назову. Но прикольно же :)

              P.S. После многих лет на динамических php/js языках - для меня строгая растовская типизация прям находка. Очень удобно. Создает много проблем так где привык работать иначе, но и решает очень много проблем там, где привык "бояться". Так что нет, не надо из питонов лепить очередной TS :)


  1. domix32
    00.00.0000 00:00

    Удивительно, что поддержка WASM появилась только сейчас. Для питона либа вроде была ещё в 18 году. Или речь идёт за появление в стандартной библиотеке?


  1. noodles
    00.00.0000 00:00

    Будущее прекрасно

    WebAssembly открывает захватывающий мир.
    ...
    в граничных вычислениях и serverless-приложениях.

    Интересно какие есть полезные serverless-приложения для широкого круга пользователей, кроме фото\видео\аудио\текстовых редакторов и игр?.)