Série d'articles
Cette fois, un exemple de gemme avec du code Rust dans la source. Les articles suivants seront utiles. Facile à faire avec Rust Ruby Gem --Qiita
Dans l'article ci-dessus, Rust a été compilé pendant le développement et le produit a été inclus dans le package gem. D'un autre côté, prenons ** comment compiler (construire) Rust lors de l'installation de gem **. Pour ce faire, bien sûr, Rust doit être installé dans l'environnement d'installation.
Exposez le prototype de gemme à GitHub. Le nom de la gemme est rust_gem_sample01. https://github.com/scivola/rust_gem_sample01
Je ne l'ai pas mis sur RubyGems.org car c'est un prototype pour montrer comment le faire.
J'avais l'intention de le rendre installable sur macOS, Linux et Windows.
Ce bijou n'est pas destiné à une utilisation pratique, il est donc aussi simple que possible. Les méthodes intégrées de Ruby et les bibliothèques attachées standard n'ont pas de fonction de génération de nombres aléatoires avec des distributions autres que la distribution uniforme, alors faisons cela. Pour le moment, celui qui renvoie un nombre aléatoire avec une distribution normale.
Des choses qui peuvent être utilisées comme ceci:
require "rust_gem_sample01"
standard_deviation = 2.0
p RustGemSample01.rand_norm(standard_deviation)
# => -3.1674786173729506
Définissez une méthode singulière rand_norm
dans le module RustGemSample01. Si un écart type est donné à l'argument, un nombre aléatoire avec une valeur moyenne de 0 est renvoyé.
La distribution normale a deux paramètres, la valeur moyenne et l'écart type, mais si vous souhaitez modifier la valeur moyenne, il vous suffit de les ajouter, donc le seul argument est l'écart type. Plus il y a d'arguments, plus le coût du FFI est élevé, donc je pense que c'est bien.
Puisqu'il n'est pas répertorié dans RubyGems.org
gem install rust_gem_sample01
Ne peut pas être installé.
Suivez les étapes ci-dessous pour cloner le référentiel et créer et installer le gem. Je voudrais savoir s'il peut être installé correctement dans divers environnements, j'apprécierais donc votre coopération.
git clone https://github.com/scivola/rust_gem_sample01.git
cd rust_gem_sample01
rake install
Cette dernière rake install
consiste à créer un paquet gem à partir du fichier de projet du gem [^ mkpkg] et à installer le gem sur votre système (c'est-à-dire globalement).
[^ mkpkg]: Le paquet gem est créé par une autre tâche Rake appelée build
, mais comme la tâche ʻinstall dépend de la tâche
build, juste
rake install` fera les deux.
Pour exécuter ce qui précède, Git doit être installé et Rust (version 1.40.0 ou supérieure) doit être installé [^ rust1_40].
[^ rust1_40]: Il n'y a absolument aucune raison de définir la version Rust sur 1.40.0 ou plus. Il ne devrait y avoir aucun problème avec une version inférieure, mais je ne sais pas ce qui s'est passé avec quelle version, j'ai donc essayé de la rendre un peu plus ancienne que la dernière version (1.46.0 pour le moment). La limite inférieure de la version est fixée autour de la 9ème ligne de ʻext / Rakefile`.
Si l'installation échoue, je pense qu'il s'agit de créer le code Rust ou de déplacer le produit de construction.
Rust est compilé au moment de l'installation, mais cela peut prendre plusieurs dizaines de secondes dans certains cas car il télécharge également les caisses dépendantes du net. Veuillez patienter même s'il n'y a pas de réponse.
Pour Linux, lorsque Ruby est installé à l'échelle du système et que gem est installé
sudo gem install hoge
Je pense que vous devez souvent ajouter sudo
comme ça.
Dans ce cas, la cargaison doit également fonctionner avec sudo. Rouille
require "rust_gem_sample01"
n = 10000
bin = Hash.new(0)
n.times do
bin[RustGemSample01.rand_norm(2.5).round] += 1
end
bin.keys.minmax.then{_1.._2}.each do |b|
puts "%4d %s" % [b, "*" * (bin[b].fdiv(n) * 60 * 4).round]
end
Cela trace un histogramme (carte de distribution de fréquence) d'un nombre aléatoire normalement distribué avec une moyenne de 0 et un écart type de 2,5 généré 10000 fois et les valeurs arrondies aux nombres entiers. Généralement, quelque chose comme ça est sorti.
-11
-10
-9
-8
-7 *
-6 **
-5 *****
-4 ***********
-3 ******************
-2 ******************************
-1 **********************************
0 ************************************
1 **********************************
2 *****************************
3 ******************
4 ***********
5 ******
6 **
7 *
8
9
10
Ouais, ça ressemble à ça.
Si le programme ne fonctionne pas et que vous obtenez une erreur, veuillez me le faire savoir.
.
├── bin
│ ├── console
│ └── setup
├── Cargo.toml
├── CHANGELOG.md
├── ext
│ └── Rakefile
├── Gemfile
├── lib
│ ├── rust_gem_sample01
│ │ └── version.rb
│ └── rust_gem_sample01.rb
├── LICENSE
├── pkg
├── Rakefile
├── README.md
├── rust_gem_sample01.gemspec
├── src
│ └── lib.rs
└── test
├── rust_gem_sample01_test.rb
└── test_helper.rb
Au fait, cette figure a été réalisée avec l'outil de ligne de commande exa réalisé par Rust.
Comme vous pouvez le voir, les fichiers pour les gemmes et les fichiers pour le package Rust sont mélangés dans la même hiérarchie.
Pour le dire brièvement, bin
, lib
, pkg
, Rakefile
, rust_gem_sample01.gemspec
, test
sont des fichiers gem.
Cargo.toml
, src
est le fichier du package Rust. Si vous construisez Rust, vous pouvez également créer un répertoire cible
.
C'est bien car le nom du fichier et le nom du répertoire ne sont pas couverts, mais je ne suis pas sûr que ce soit un bon style. L'échantillon de Rutie avait ce genre de structure (je pense). Au moins à première vue, je ne pense pas vraiment que cela puisse être distingué immédiatement. Vous voudrez peut-être pousser la relation Rust dans un répertoire [^ files].
[^ files]: Si vous voulez créer une gemme à partir de zéro, il peut être plus facile de travailler avec. D'abord, faites bundle gem hoge
pour créer un projet gem, puis allez à l'intérieur et faites cargo new fuga --lib
pour créer un projet Rust.
Le code Rust est un paquet avec seulement la caisse de bibliothèque rand_distr_for_ruby
.
Caisse à charge
Cargo.toml
[dependencies]
rand = "0.7.3"
rand_distr = "0.3.0"
Seulement.
rand est familier. rand_distr est une distribution rand
. Il semble que la fonction qui génère le nombre aléatoire de soit indépendante (je ne sais pas).
C'est le seul corps de code:
src/lib.rs
use rand_distr::{Normal, Distribution};
#[no_mangle]
pub extern fn rand_norm(variance: f64) -> f64 {
let normal = Normal::new(0.0, variance).unwrap();
normal.sample(&mut rand::thread_rng())
}
Il semble inutile de générer rand_distr :: Normal
chaque fois qu'il est appelé.
Eh bien, ce n'est pas à des fins pratiques.
Si vous souhaitez générer un grand nombre de nombres aléatoires avec la même distribution, vous ne souhaitez le faire qu'une seule fois. Il est facile de garder le rand_distr :: Normal
généré tout le temps avec Rutie (voir [(5)](voir https://qiita.com/scivola/items/2ab41a03beb00f6ce3d2)), mais uniquement avec FFI. Je ne sais pas trop comment faire.
À propos, pendant le développement, dans cet état
cargo check
Mais
cargo build --release
Je peux le faire. L'avantage de cette structure de répertoires est que vous n'avez pas à aller et venir entre les hiérarchies en plaçant les fichiers Rust dans le répertoire racine de votre projet.
C'est le code de base du côté Ruby.
rust_gem_sample01/lib/rust_gem_sample01.rb
require "ffi"
require "rust_gem_sample01/version"
module RustGemSample01
extend FFI::Library
lib_name = "rand_distr_for_ruby"
file_name =
case RbConfig::CONFIG["host_os"].downcase
when /darwin/ then "lib#{lib_name}.dylib"
when /mingw|mswin/ then "#{lib_name}.dll"
when /cygwin/ then "cyg#{lib_name}.dll"
else "lib#{lib_name}.so"
end
ffi_lib File.expand_path(file_name, __dir__)
attach_function :rand_norm, [:double], :double
end
J'utilise un gem appelé ffi pour attribuer des fonctions Rust aux méthodes Ruby. FFI (Foreign Function Interface), qui facilite la gestion d'un mécanisme d'échange entre différentes langues.
La plupart du code de cette définition de module est consacré à la détermination des arguments à passer à la méthode ffi_lib
.
Passez le chemin du fichier de bibliothèque à lire à cette méthode.
Le fichier de bibliothèque créé en compilant avec Rust a un nom de fichier légèrement différent selon le système d'exploitation, donc c'est compliqué comme ça.
Je ne sais pas si ce processus convient vraiment, mais le code Rutie [rutie-gem / lib / rutie.rb](https://github.com/danielpclark/rutie-gem/blob/ffaba4689f351a0acaf84067a6fca5c3b65bc9aa/lib/rutie. J'ai fait référence à rb).
attach_function :rand_norm, [:double], :double
La partie de est de générer des méthodes Ruby dans le module en fonction de la bibliothèque chargée.
Le premier argument est le nom de la fonction de bibliothèque. C'est également le nom de la méthode Ruby.
Le deuxième argument est le type d'argument de fonction. Puisqu'il peut y avoir plusieurs arguments, donnez-les sous forme de tableau. : double
est le nom du type défini dans la spécification FFI et représente un nombre à virgule flottante double précision. Je n'en sais pas grand-chose, mais c'est probablement le langage C double
. Ruby's Float le soutient.
Le troisième argument est le type de retour de la fonction.
C'est tout ce qui est vraiment nécessaire pour utiliser les fonctions de la librairie créée par Rust côté Ruby via FFI. Facile.
gemspec
Voici l'endroit qui nécessite une explication spéciale sur gemspec.
rust_gem_sample01/rust_gem_sample01.gemspec
#Extrait
Gem::Specification.new do |spec|
#Omission
spec.add_dependency "ffi", "~> 1.13.1"
spec.extensions = %w[ext/Rakefile]
end
Puisque nous utilisons ffi gem, il est naturel de l'ajouter à la dépendance d'exécution (ʻadd_dependency`), mais l'important est le suivant
spec.extensions = %w[ext/Rakefile]
Au fait. Qu'est-ce que c'est?
Il s'agit du chemin d'accès au fichier Rake qui décrit ce qu'il faut faire lors de l'installation du gem. Dans le cas des extensions C, le rôle est généralement pris en charge par un fichier appelé ʻextconf.rb`. Pour le gem sqlite3, il s'agit de sqlite3-ruby / ext / sqlite3 / extconf.rb. Je ne suis pas sûr du tout, mais il semble que l'extension C fasse quelque chose comme la compilation de C en fonction de ce fichier.
Cependant, il semble que ʻextconf.rb` soit basé sur C, et je ne sais pas comment l'écrire dans le cas de Rust. En fait, j'ai senti qu'il ne pouvait pas être utilisé avec Rust en premier lieu. Quand j'ai cherché d'autres moyens, j'ai trouvé qu'il y avait aussi un moyen d'utiliser la tâche Rake. Ceci sera décrit en détail dans la section suivante.
Rakefile
Il semble que ce que vous avez écrit comme tâche default
dans le fichier Rake spécifié comme` spec.extensions = ʻin gemspec est exécuté au moment de l'installation.
rust_gem_sample01/ext/Rakefile
#Que faire lors de l'installation de gem
#Écrire comme tâche par défaut
task :default do
#Rust est-il installé?
#Déterminez si la commande cargo fonctionne
begin
cargo_v = `cargo -V`
rescue Errno::ENOENT
raise "Cargo not found. Install it."
end
#La version Rust (correspondant à la version Cargo) est-elle au-dessus d'un certain niveau?
cargo_version = cargo_v.match(/\Acargo (\d+)\.(\d+)\.(\d+) /)[1..3].map(&:to_i)
if (cargo_version <=> [1, 40, 0]).negative?
raise "Too old Cargo (ver. #{cargo_v}). Update it."
end
#Construire la rouille
system "cargo build --release", chdir: __dir__ + "/.."
#Nom du fichier produit
#Dépend du système d'exploitation
lib_name = "rand_distr_for_ruby"
file_name =
case RbConfig::CONFIG['host_os'].downcase
when /darwin/ then "lib#{lib_name}.dylib"
when /mingw|mswin/ then "#{lib_name}.dll"
when /cygwin/ then "cyg#{lib_name}.dll"
else "lib#{lib_name}.so"
end
#Lib du produit/Déplacez-vous directement en dessous
FileUtils.mv __dir__ + "/../target/release/#{file_name}", __dir__ + "/../lib/"
FileUtils.rmtree __dir__ + "/../target/"
end
J'ai écrit un commentaire dans le code, mais ce que je fais est essentiellement
Et juste ça.
Je suis curieux que l'endroit pour obtenir le nom de fichier du produit de construction de code Rust soit lib / rust_gem_sample01.rb
et non DRY.
Eh bien, il peut être correct de déplacer tous les fichiers directement sous target / release /
.
À propos, si vous construisez avec Rust, vous pouvez créer divers fichiers en cours de téléchargement et de compilation des bibliothèques nécessaires. Dans le cas de cette gemme, un fichier d'environ 18 Mo est généré au moment de la construction. Tout ce que je veux, c'est un fichier (environ 300 Ko cette fois), et tout le reste est inutile.
Donc la dernière ligne de la tâche
FileUtils.rmtree __dir__ + "/../target/"
Est nettoyé par.
Pour les extensions C, il y a débat sur la question de savoir si les gemmes devraient inclure des fichiers pré-compilés pour la distribution.
référence:
Il y a des avantages et des inconvénients, mais qu'en est-il de Rust? Peut-être que vous choisissez généralement la pré-compilation. Parce que de nombreux utilisateurs de Ruby n'ont pas d'environnement de compilation Rust.
Donc, dans cet article, j'ai osé essayer une méthode que beaucoup de gens ne feraient pas.