Rust : découvrez Wasm-bindgen, pour faciliter l'interaction entre les modules Wasm et JavaScript,
Par Anthony Defranceschi

Le , par Songbird_, Rédacteur/Modérateur
WebAssembly est, avant tout, un standard pensé dans l’optique d’améliorer les performances de JS et d’établir un socle commun qui pourra, à l’avenir, être exploité par une variété de langages. Les premiers à communiquer avec wasm étant C, C++ ainsi que Rust.

Dans ce billet, je m’efforcerai de détailler les (chouettes) solutions mises en place pour faciliter cette intégration. C’est parti !

Un peu de contexte...

Actuellement, il existe trois principaux outils supportant la compilation, vers wasm, des langages dont je parlais plus haut :
  1. C et C++: Emscripten (site web, dépôt) et Binaryen (site web, dépôt). L’un est une importante toolchain qui n’a fait "que" ajouter un nouvel élément à son trousseau, alors que l’autre a été créé par l’équipe en charge du développement et de la démocratisation de WebAssembly. N’étant pas l’objet de ce billet, je vous invite vivement à consulter leur site web respectif, où vous pourrez y connaître les différences techniques des deux projets ;
  2. Rust: wasm-bindgen, qui est certainement le moins mature, pour le moment, des trois.


Notez que Emscripten et Binaryen sont axés sur C et C++. Ça ne signifie, cependant, pas qu’ils ne supporteront jamais d’autres langages et c’est d’ailleurs l’un des objectifs partagés par wasm-bindgen qui, à terme, ne devra plus être directement lié au langage Rust.

Sa structure

En réalité, le projet est composé de deux éléments:

  1. La crate wasm-bindgen. Elle nous apporte l’attribut modestement nommé #[wasm-bindgen] qui va servir d’interprète pour les deux partis (nous y reviendrons rapidement) ;
  2. L’outil wasm-bindgen-cli qui n’est ni plus ni moins que le programme qui va nous permettre de rendre nos métadonnées (générées par l’attribut cité plus haut) intelligibles en wasm.


Petit prix à payer en revanche, ces fonctionnalités ne sont disponibles qu’en compilant avec la nightly de Rust.

Son fonctionnement

Les présentations ainsi faites, passons à des choses plus intéressantes !

L’attribut wasm-bindgen

Du côté de notre crate, #[wasm-bindgen] servira à "marquer" les services, structures et implémentations (impl) puis préparera à l’exportation tout ce petit monde sous la forme d’un module ECMAScript, facilitant, ensuite, largement leur importation dans le code JavaScript censé les accueillir.


Exporter des composants écrits en Rust vers JS

Prenons, dans un premier temps, les exemples originaux.

Côté Rust, dans lib.rs :
Code Rust : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  
// Current prelude for using `wasm_bindgen`, and this'll get smaller over time! 
#![feature(proc_macro, wasm_custom_section, wasm_import_module)] 
extern crate wasm_bindgen; 
use wasm_bindgen::prelude::*; 
  
// Here we're importing the `alert` function from the browser, using 
// `#[wasm_bindgen]` to generate correct wrappers. 
#[wasm_bindgen] 
extern { 
    fn alert(s: &str); 
} 
  
// Here we're exporting a function called `greet` which will display a greeting 
// for `name` through a dialog. 
#[wasm_bindgen] 
pub fn greet(name: &str) { 
    alert(&format!("Hello, {}!", name)); 
}

Côté JavaScript:

Code JavaScript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
const { greet } = wasm_bindgen; 
  
function runApp() { 
  greet('World'); 
} 
  
// Load and instantiate the wasm file, and we specify the source of the wasm 
// file here. Once the returned promise is resolved we're ready to go and 
// use our imports. 
wasm_bindgen('../out/main_bg.wasm').then(runApp).catch(console.error);

Vous pouvez remarquer à la ligne 9 que le bloc extern {}, couramment utilisé avec la FFI, dispose de la même fonction qu’en temps normal: déclarer les signatures des services que nous tentons d’utiliser à partir d’une ABI connue. Ici, nous souhaitons user de la méthode window.alert() que nous propose le navigateur.

Maintenant, nous pourrions toujours réadapter cet exemple pour illustrer ce qui a été dit juste avant: il nous est possible d’exporter des structures et l’implémentation de leurs services.

Côté Rust, dans lib.rs :

Code Rust : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#[wasm_bindgen] 
extern { 
    fn alert(s: &str); 
} 
  
#[wasm_bindgen] 
pub struct Foo; 
  
#[wasm_bindgen] 
impl Foo { 
  pub fn new() -> Foo { 
    Foo 
  } 
  pub fn greet(&self, name: &str) { 
      alert(&format!("Hello, {}!", name)); 
  } 
}

Côté JavaScript:

Code JavaScript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
/* 
On récupère la structure exactement comme 
on pourrait le faire avec un module ECMAScript 
écrit en JavaScript. 
*/ 
const { Foo } = wasm_bindgen; 
  
function runApp() { 
  Foo.new().greet('World'); 
} 
  
wasm_bindgen('../out/main_bg.wasm').then(runApp).catch(console.error);

Exporter des composants écrits en JS vers Rust

#[wasm_bindgen] dispose de trois paramètres (dont un variable) :

  1. module: renseigne le chemin à partir duquel le compilateur est censé charger le module ECMAScript ;
  2. constructor: précise que la signature, suivie par ce paramètre, représente le constructeur du prototype ;
  3. method: précise que la signature, suivie par ce paramètre, représente une méthode du prototype.


Ce trio vous permet d’importer des structures initialement écrites en JS.

Côté Rust :

Code Rust : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// On renseigne le nom du module. 
#[wasm_bindgen(module = "main")] 
extern { 
    fn alert(s: &str); 
    // Ici, on se sert du mot-clé `type` 
    // pour préciser que l'on souhaite importer 
    // la structure `MyJavaScriptObject`. 
    type MyJavaScriptObject; 
    // On définit la signature du constructeur. 
    #[wasm_bindgen(constructor)] 
    fn new() -> MyJavaScriptObject; 
    // On définit l'identificateur et la signature 
    // de la méthode d'instance. 
    #[wasm_bindgen(method)] 
    fn say_hello(this: &MyJavaScriptObject) -> (); 
} 
  
#[wasm_bindgen] 
pub fn greet(name: &str) { 
    alert(&format!("Hello, {}!", name)); 
    MyJavaScriptObject::new().say_hello(); 
}

Côté JavaScript :

Code JavaScript : Sélectionner tout
1
2
3
4
5
6
7
8
9
export class MyJavaScriptObject { 
  constructor() { 
    this.greetings = "Hi, I'm a JavaScript object!"; 
  } 
  
  say_hello() { 
    console.log(this.greetings); 
  } 
}




Mon avis

J’aimerais faire un retour qui servira de conclusion à ce petit billet, si ça peut aider certains développeurs à situer WASM/Rust en termes de maturité.

Nous avons une vision basique de ce qu’un développeur utilisant wasm est censé devoir faire pour lier JS à un module wasm. Rien de très compliqué, en somme, mais les possibilités me semblent encore limitées.

Ajoutons à cela que la compréhension même du langage n’est pas encore très bonne. Les lifetimes ne sont pas supportées, les constantes ne peuvent être exportées et les wrappers tels que Option et Result ne sont pas disponibles. En bref, y’a du boulot.

Source


Voir aussi



Vous avez aimé cette actualité ? Alors partagez-la avec vos amis en cliquant sur les boutons ci-dessous :


 Poster un commentaire

Avatar de gb_68 gb_68 - Membre confirmé https://www.developpez.com
le 31/07/2018 à 10:58
Merci pour ces informations.

Juste une petite précision sur Emscripten et Binaryen ; ce sont deux outils complémentaires : Emscripten utilise Binaryen comme back-end pour générer du wasm - même si un back-end LLVM wasm est aussi prévu (le but de Binaryen est, d'après ce que j'ai compris, essentiellement d'être un ensemble d'outils/bibliothèques pour aider à réaliser des compilateurs/outils supportant wasm). C'est d'ailleurs une bonne chose que des projets modulaires pouvant se réutiliser les uns les autres apparaissent plutôt qu'une prolifération d'outils monolithiques dont les rôles se chevauchent ; car même si les acteurs majeurs du Web sont favorables à WebAssembly, c'est un projet ambitieux qui nécessitera d'importantes ressources pour arriver à terme (autant ne pas trop les gaspiller).

Emscripten
Citation Envoyé par Emscripten
Emscripten’s WebAssembly support depends on Binaryen
Binaryen
Citation Envoyé par Binaryen
Integrate with Emscripten in order to provide a complete compiler toolchain from C and C++ to WebAssembly.
Avatar de Songbird_ Songbird_ - Rédacteur/Modérateur https://www.developpez.com
le 06/08/2018 à 4:44
Bonjour,

Effectivement, y'a eu une petite méprise. Je corrigerai ça dans la journée, merci !

Maintenant que tu le dis, ça coule de source en effet.

En te souhaitant une bonne journée !
Contacter le responsable de la rubrique Accueil