Bref, j'ai créé une bibliothèque Rust et un moteur iBus (et je cherche comment les empaqueter)

Posté par (page perso) . Édité par palm123, BAud, Nils Ratusznik, Benoît Sibaud et Thomas Debesse. Modéré par tankey. Licence CC by-sa
21
14
oct.
2014
Technologie

Tiré du journal d'Allan Simon qui donne un exemple de bibliothèque en langage Rust et comment créer des bibliothèques Rust appelables :

Pour le décideur pressé, j'ai écrit:

  • une bibliothèque en Rust, compatible ABI C (c-à-d un joli .so et .h) pour manipuler du pinyin : librustpiniyn ;
  • un moteur iBus pour taper chinois en précisant les tons, utilisant la bibliothèque du dessus : ibus-pinyintone ;
  • un ensemble d'exemples sur comment créer des bibliothèques Rust appelables comme si c'était une lib C : ffi-rust.

Et je me demande comment je pourrais empaqueter les deux premiers dans un joli .deb qui va bien.

Sommaire

Contexte

Tu as sûrement entendu parler de Rust, ce nouveau langage de programmation hype, tellement hype qu'il n'est pas encore stable, écrit par Mozilla.

Les promesses de Rust qui m'ont séduit :

  • (quasi) sans-runtime : rien ne se passe dans votre dos, possibilité de distribuer des binaires compilés depuis Rust sans devoir faire installer à l'utilisateur une JVM de plus ;
  • langage compilé : si vous pouvez lancer le binaire il y a peu de chances que ca plante dans les 2 premières secondes parce que vous avez fait une coquille ;
  • langage fortement typé avec déduction automatique des types : le beurre et l'argent du beurre, pas d'arguments qui sont parfois des entiers, parfois des chaines de caractères car votre collègue incompétent a parfois fait le parseint avant, parfois après, parfois pas, tout en n'ayant pas besoin de préciser le type à chaque fois que c'est évident ;
  • possibilité d'être compatible avec l'ABI C ;
  • une librairie standard très complète (des primitives pour faire du code multi-tâches, un décodeur json intégré, que du bonheur)  ;
  • des structures et des traits, pas d'héritage ;
  • possibilité de faire les choses de manière fonctionelle (closure etc.) ;
  • performance qui rivalise avec C++ (tout du moins à terme, mais c'est déjà plus ou moins le cas) ;
  • un compilateur très strict avec des jolis messages d'erreurs très lisibles ;
  • une communauté vibrante (merci #rust sur IRC) ;
  • un système de build et de gestion de dépendances simplissime (cargo build et c'est bon).

Bref, du coup je me suis dit que ce langage serait parfait pour moi qui aime le C++ (surtout depuis C++11) et Python, en gros le meilleur de ces deux mondes réunis.

J'ai donc écrit pendant mes vacances, avec l'aide d'un ami, une bibliothèque Rust pour convertir du pinyin, qui est en gros la transcription phonétique standardisée en alphabet latin du Chinois mandarin, en caractères chinois. par exemple pouvoir convertir ni3hao3 en 你好.

La bibliothèque une fois créée sera utilisée par un moteur iBus (iBus étant le système le plus commun sous Linux pour taper des langues qui se tapent mal avec un clavier standard, ibus offrant les briques communes, capture des entrées clavier etc. , et les moteurs eux offrent la logique spécifique à une langue, par exemple on peut avoir iBus installé avec un moteur pour le chinois, et un moteur pour le japonais).

Les moteurs iBus étant le plus souvent écrits soit en C, soit en Python (iBus n'offrant les bindings que pour ces deux langages à ma connaissance, et quand bien, le peu de code d'exemple est écrit dans l'un de ces deux langages), et ayant trouvé un exemple de template de moteur ibus-tmpl en C, il était plus simple d'avoir toute la logique en Rust (isolé dans la bibliotheque) et la glue du moteur en C pour réutiliser le code d'exemple.

Comment écrire une bibliothèque Rust, compatible ABI C, sans runtime (i.e avec un joli .so et .h a la fin)

Les exemples (ainsi que d'autres plus complexes) peuvent être retrouvés sur le projet github https://github.com/allan-simon/ffi-rust.

Générer un .so avec Rust

Il y a deux manières de procéder, soit directement dans le fichier Rust .rs ou en le précisant dans le fichier Cargo.toml (le "MakeFile" de rust).

Pour les petits projets, il suffit simplement de mettre #![crate_type = "dylib"] au début du fichier :

#![crate_type = "dylib"]
pub extern fn hello_world() {
   println!("hello world");
}

pub extern est là pour dire que la fonction doit être exportée, ainsi que pour dire au compilateur de ne pas s'inquiéter s'il ne voit pas la fonction hello_world utilisée, que ce n'est pas du code mort.

Pour le fichier cargo, cela donne :

[package]
name = "votre_lib_qui_va_bin"
version = "0.0.1"
authors = [ "Votre Nom <votre@email.com>" ]
[lib]
name = "nomdelalib"
path = "src/lib.rs"
crate-type = ["dylib"]

et cela génère dans les deux les deux un cas un fichier .so qui va bien, mais appelable seulement depuis un autre projet Rust.

Rendre les noms de fonctions compatibles avec l'ABI C

Pour pouvoir appeler la fonction Rust depuis du C, du Python etc., il faut que son nom soit prédictible, pour cela, hyper simple, il suffit de rajouter la directive #[no_mangle] au-dessus de la fonction, ce qui nous donne :

#![crate_type = "dylib"]

#[no_mangle]
pub extern fn hello_world() {
   println!("hello world");
}

et voila, rien de plus rien de moins, et vous avez à présent un .so compatible ABI C, ce qui vous permet par exemple de l'appeler depuis Python en faisant :

import ctypes
votrelib = ctypes.CDLL("libvotrelib.so")
votrelib.hello_world()

magique, non ?

Faire des choses plus compliquées

Je ne rentrerai pas dans de longs détails ici, juste qu'il est assez simple de créer une bibliothèque, même très complexe depuis C en suivant les conseils ci-dessous :

  • il est assez simple d'échanger des int / string dans les deux sens entre Rust et C (avec une légère conversion à faire pour les string, comme elles finissent par \0 en C et pas en Rust) ;
  • pour les types plus complexes (du style HashMap etc.), essayer au maximum de tout faire en Rust, et de n'utiliser C que pour transporter le pointeur sur la structure d'un appel Rust à un autre.

Si des personnes sont intéressées je verrai peut-être pour écrire un guide plus détaillé sur des exemples plus poussés (passage de callback, comment gérer la mémoire etc., comment se passer du runtime etc.).

ibus-pinyintones : pour les personnes qui apprennent le Chinois et oublient toujours les tons

pourquoi un moteur alternatif pour écrire le chinois

Cette partie est un peu moins technique : je pense que beaucoup moins de personnes sont intéressées par "comment écrire votre propre moteur iBus". Le but était pour moi qui apprend le Chinois d'avoir un moyen de me forcer à me souvenir des "tons" des mots Chinois.

Pour ceux qui ne connaissent pas le Chinois, les tons sont une composante super importante du Chinois oral. Pour donner une similarité avec le Francais, lisez a voix haute "Tu viens manger." et "Tu viens manger ?" : vous remarquez que pour la question on monte le ton en fin de phrase, ce qui permet a l'oral de distinguer la question de l'affirmation. Maintenant, imaginez en Chinois le même concept mais syllabe par syllabe et non plus pour savoir savoir si une question est affirmative ou interrogative mais tout simplement pour savoir quel caractère Chinois c'est.

Le ton est super important en Chinois, car le nombre de sons en chinois est très limité. Cependant à la saisie sur ordinateur ou téléphone, souvent on entre la phonétique sans le ton (et le moteur de saisie se charge des ambigüités, en classant les propositions par fréquence, et en s'aidant des mots tapés avant). Cela a donc le fort désavantage de ne pas demander la connaissance du ton, ce qui fait que l'on peut parler sur Skype/mails de manière quasi parfaite, tout en étant incompréhensible a l'oral.

Caractéristique de ibus-pinyintone, différence avec les moteurs habituels

Ici il faut donc taper obligatoirement le ton.

On peut taper "n3h3" ou "ni3hao3" pour avoir 你好, mais le nombre (c.a.d le ton) doit être présent. Cela demande donc un poil plus de frappes, mais cela est compensé par le fait qu'ainsi le nombre d'homonynes est très très fortement réduit et réduit le nombre de sélections manuelles que l'on a à faire avec les touches multidirectionnelles

Le prochain objectif est d'ajouter la prédiction du mot suivant, par exemple si je tape "je mange une" , que le moteur propose directement "pomme" "poire" "pizza" (ce que ne propose pas ibus-pinyin par exemple, et encore une fois, ce qui avec l'aide des tons, permettrait de rendre la prédiction plus efficace)

Demande d'aide : comment créer un paquet Debian de tout cela

Voilà, j'arrive à la toute fin et le principal but de mon journal (je vous ai bien eu, en fait je voulais juste de l'aide, mais je ne voulais pas poster dans la catégorie forum) : j'aimerais avoir un peu d'aide pour empaqueter mon moteur et ma bibliothèque.

J'ai commencé à lire le guide pour empaqueter sous Debian mais voir que faire un paquet pour une bibliothèque est mis dans la catégorie "tâches difficiles", et que j'avoue être un peu flemmard, si une bonne âme se sent de m'aider, j'en serais très reconnaissant.

Vers l'infini et l'au-delà

À l'avenir je vais essayer de rapidement porter de nouveau les moteurs iBus que j'avais écrits il y a très longtemps pour l'anglais et le français (base sur une intégration d'aspell dans ibus), de manière à pouvoir taper les accents sur mon qwerty sans encombre et éviter les fautes de dyslexie du clavier que je fais souvent.

  • # dur dur

    Posté par (page perso) . Évalué à 4.

    Hello Allan ;)

    Rust n'étant pas encore dans Debian, ça va être difficile d'avoir ta bibliothèque dans l'archive…

    • [^] # Re: dur dur

      Posté par . Évalué à 5.

      Il y a une équipe rust, mais elle utilise un PPA, il faudra voir avec eux.
      Éventuellement pour le moment créer un dépôt externe à Debian le temps que Rust arrive.

      Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

      • [^] # Re: dur dur

        Posté par (page perso) . Évalué à 1.

        En effet, je viens de voir cela (d'ailleurs il me semble que Sylvestre fait partie de l'equipe rust si je ne m'abuse)

        Je pense que je vais suivre ton conseil et pour l'instant partir avec un depot externe et je reverrais cela une fois que rust sera dans les depots.

        • [^] # Re: dur dur

          Posté par . Évalué à 1.

          Si j’ai bien compris, il n’y a pas de « runtime » Rust. Ta bibliothèque est quand même dépendante de bibliothèque standard fournies par rust, c’est ça ?

          Que donne ldd sur le .so produit ?

          Mes commentaires sont en wtfpl. Une licence sur les commentaires, sérieux ? o_0

          • [^] # Re: dur dur

            Posté par . Évalué à 3.

            Éventuellement, il peut peut être proposer une version binaire de son paquet, mais il manquera la version source (je ne connais pas la politique de Debian sur le sujet).

            Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

            • [^] # Re: dur dur

              Posté par . Évalué à 1.

              Même s’il ne le fait pas intégrer par la distrib dans un premier temps, rien que fournir un .deb est vachement plus sympa pour l’utilisateur final.

              Et certes, packager une lib demande un peu d’attention, mais ce n’est pas non plus insurmontable. La doc n’est pas toujours facile à trouver par contre, et le shlibdeps / makeshlibs peut se révéler un véritable enfer…

              Mes commentaires sont en wtfpl. Une licence sur les commentaires, sérieux ? o_0

              • [^] # Re: dur dur

                Posté par . Évalué à 3.

                J'ai jamais dis le contraire, hein.

                Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

          • [^] # Re: dur dur

            Posté par (page perso) . Évalué à 1.

            Le ldd pour la version actuelle donne

                linux-gate.so.1 =>  (0xb7711000)
                /lib/i386-linux-gnu/liblsp.so (0xb76b9000)
                libnative-4e7c5e5c.so => /usr/local/lib/libnative-4e7c5e5c.so (0xb764f000)
                libserialize-4e7c5e5c.so => /usr/local/lib/libserialize-4e7c5e5c.so (0xb7590000)
                liblog-4e7c5e5c.so => /usr/local/lib/liblog-4e7c5e5c.so (0xb7580000)
                libstd-4e7c5e5c.so => /usr/local/lib/libstd-4e7c5e5c.so (0xb736d000)
                librustrt-4e7c5e5c.so => /usr/local/lib/librustrt-4e7c5e5c.so (0xb722a000)
                libgcc_s.so.1 => /lib/i386-linux-gnu/libgcc_s.so.1 (0xb71e8000)
                libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7037000)
                libdl.so.2 => /lib/i386-linux-gnu/libdl.so.2 (0xb7032000)
                libsync-4e7c5e5c.so => /usr/local/lib/libsync-4e7c5e5c.so (0xb6f9b000)
                libpthread.so.0 => /lib/i386-linux-gnu/libpthread.so.0 (0xb6f7f000)
                /lib/ld-linux.so.2 (0xb7712000)
                libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xb6f39000)
                libregex-4e7c5e5c.so => /usr/local/lib/libregex-4e7c5e5c.so (0xb6ef2000)
            

            je dis "pour la version actuelle" car apres discussion sur #rust, je pense que je vais pouvoir retirer les dependances sur librustrt et libstd (et surement d'autre par la meme occasion), apres si j'ai moi meme bien compris le fonctionnement c'est que par defaut le compiltateur rust va te link avec la libstd de rust , (sauf si tu precises un "no_std" dans ton projet), ensuite le but est d'avoir les grosses categories de fonctionnalites dans des .so separer de facon a terme de n'avoir a linker qu'avec ceux dont tu as besoin.

            je vais essayer ce weekend de nettoyer un peu mon projet pour ne plus dependre de std et je reposterai le resultat.

      • [^] # Re: dur dur

        Posté par (page perso) . Évalué à 2.

        Il y a une équipe rust, mais elle utilise un PPA, il faudra voir avec eux.

        Le PPA est d'une qualité éloignée de ce que l'on attend dans Debian…

        Et non, je suis pas dans l'équipe Rust mais je me suis intéressé à son packaging (et je le suis toujours).

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.