Si vous êtes développeur node.js vous vous confronterez tôt ou tard au développement d'addons natifs ... et à tout un lot de nouveaux problèmes : langage C++ en lui même, mais également API V8, build system ou dépendance aux versions de V8. Pourtant dès que vous avez besoins de multi-threading, d'accès aux ressources machine ou même simplement pour réutiliser des librairies existantes vous n'y coupez pas. Lors de ce talk vous découvriez comment le langage Rust grâce à Neon permet de rendre vos modules natifs "fast & safe", vous pourriez même prendre du plaisir à développer des modules natifs dorénavant
1. Rust My Node
Ou pourquoi tout développeur Node.js devrait apprendre Rust
2. What I do
Thomas Haessle
@Oteku
https://oteku.github.io/
cutii.io
Cutii au coeur
de la ville
Création
d’un réseau
d’animateurs
locaux
Implication
des agents
publics
Villes
numériques
Valoriser le
patrimoine
MUSEUM
www.cutii.io
Cutii est une plateforme de mise en relation à distance pour les
personnes isolées ou dépendantes avec leur famille et une communauté
regroupant particuliers, aidants, associations et médecins.
Communauté solidaire
CUTII RÉSEAU
Activités premium Recréer du lien social Rencontres citoyennes
3. Le meilleur runtime web
Javascript application Javascript modules
Node.js Standard library
Node.js bindings
Javascript VM Event loop, Async I/O, Thread pooling
Http parser
OpenSSL
zlib
DNS
OS Non blocking I/O call
epollIOCP kqueue
Node addons
4. Node
• Node est monothreadé pour VOTRE code JS ”(ᴗ_ ᴗ。)
• Il y a un thread pour libuv, qui gère lui même un poll de thread pour
les I/O asynchrone (4 par défaut, jusque 128) (∩`-´)⊃━☆゚.*・。゚ et
communique avec node grace à des event emitter et des callbacks
• Chaque version de node est liée à une version du moteur v8
( ̄^ ̄)ゞ
5. Addons
• Des « dynamically-linked shared objects », écrits en C++, en
utilisant l’api V8 api, qui peuvent être importé par la
fonction require() (๑>ᴗ<๑)
• Des modules compilés dépendant de V8
=> le build dépend de la version de node et de l’OS (╬ Ò ‸ Ó)
• Depuis Node 8, il existe n-api, une abstraction indépendante
de la version de V8 ᕦ(ò_óˇ)ᕤ
… mais ce n’est toujours pas la norme (╥_╥)
6. Quand & Pourquoi
• Réutiliser du code C ou C++ existant
• Utiliser des librairies binaires : statiques (.a) ou dynamiques
(.so / .dylib / .dll)
• Viser des performances natives
• Avoir accès aux ressources systèmes (I/O, ports séries, GPU, …)
• Utiliser des worker threads pour des algo plus intéressant en
multithreading
• Typer statiquement une librairie
7. Anti patterns
• Ré-écrire l’event loop
• Faire des I/O intensif : libuv sera meilleur
• Seulement typer un programme :
- les approches « js as a bytecode » seront meilleures
(js_of_ocaml, Kotlinjs, Typescript, Purescript,…)
- Ou même WASM (Rust wasm-pack)
9. Mon réflexe fasse au CPP ?
Rust : sauve moi !
Rust features in "short" :
• Memory Safe
• Compiler that block lot of runtime errors
• Interface with C/C++
• Generic
• Polymorphism
• No garabage collector
• No manual memory allocation / desallocation
• No segmentation fault
• No data race
• Amazing toolchain
• Compile to native apps, libs, webassembly or even node native addons.
10. Immutablité et ADT
type coord3d = (i32, i32, i32);
let warehouseCoord: coord3d = (1, 4, 18);
let mut warehouse2Coord: coord3d = (1, 4, 18);
warehouse2Coord = (2, 7, 18);
struct Hero {
age: u8,
name: String,
}
let name = String::from("Jaime Lanister");
let jaime = Hero {
name, // punning
age: 32,
};
enum LifeVariant {
Alive(Hero),
Dead,
WhiteWalker
}
11. Pattern Matching
let how_is_jaime = LifeVariant::Alive(jaime);
fn get_message(how_are_you: LifeVariant) -> String{
match how_are_you {
LifeVariant::WhiteWalker => String::from("Aaaaaarg !"),
LifeVariant::Dead => String::from("!!!"),
LifeVariant::Alive(h) => String::from("Great! ") + &h.name + " is alive",
}
};
assert_eq!(get_message(how_is_jaime), String::from("Great! Jaime Lannister is alive"));
12. No Null - Option & Result
type Weapon = Option<String>;
type MyError = &'static str;
fn strike(who : Hero, with: Weapon) -> Result<LifeVariant, MyError> {
if who.name.is_empty() {
Err("No One can't be killed")
}else{
if let None = with {
Ok(LifeVariant::Alive(who))
}else{
Ok(LifeVariant::Dead )
}
}
}
13. Génériques et macros
fn compose_two<A, B, C, G, F>(f: F, g: G) -> impl FnOnce(A) -> C
where
F: FnOnce(A) -> B,
G: FnOnce(B) -> C,
{
move |x| g(f(x))
}
macro_rules! compose {
( $last:expr ) => { $last };
( $head:expr, $($tail:expr), +) => {
compose_two($head, compose!($($tail),+))
};
}
fn main() {
let double = |x| x * 2;
let add2 = |x| x + 2;
let double_then_add2 = compose!(double, add2);
println!("Result is {}", double_then_add2(10));
}
14. Traits (« Type classes »)
struct Scalar<T>(T);
use std::ops::BitOr;
impl<A, B, F> BitOr<F> for Scalar<A>
where F: FnOnce(A) -> B
{
type Output = Scalar<B>;
fn bitor(self, f: F) -> Scalar<B> {
Scalar(f(self.0))
}
}
impl<T> Scalar<T> {
fn unwrap(self) -> T{
self.0
}
}